diff --git a/Cargo.lock b/Cargo.lock index 84690c6b..6fe9699a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -387,7 +387,7 @@ dependencies = [ "alloy-sol-macro-input", "const-hex", "heck 0.4.1", - "indexmap", + "indexmap 2.2.5", "proc-macro-error", "proc-macro2", "quote", @@ -799,6 +799,26 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" +[[package]] +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags 2.4.2", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.52", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -973,6 +993,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -998,6 +1027,17 @@ dependencies = [ "inout", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "coins-bip32" version = "0.8.7" @@ -1448,6 +1488,15 @@ dependencies = [ "ark-ff 0.4.2", "eigen-crypto-bls", "eigen-crypto-bn254", + "eigen-metrics-collectors-economic", + "eigen-metrics-derive", + "eyre", + "hyper 0.14.28", + "metrics", + "metrics-exporter-prometheus", + "prometheus", + "reqwest 0.12.4", + "tokio", ] [[package]] @@ -1457,14 +1506,32 @@ dependencies = [ "alloy-primitives", "eigen-client-avsregistry", "eigen-client-elcontracts", + "eigen-metrics-derive", "ethers", + "metrics", + "metrics-process", ] [[package]] name = "eigen-metrics-collectors-rpc-calls" version = "0.0.1-alpha" dependencies = [ - "ethers", + "eigen-metrics-derive", + "metrics", +] + +[[package]] +name = "eigen-metrics-derive" +version = "0.0.1-alpha" +dependencies = [ + "metrics", + "once_cell", + "proc-macro2", + "prometheus", + "quote", + "regex", + "syn 2.0.52", + "trybuild", ] [[package]] @@ -2250,7 +2317,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap", + "indexmap 2.2.5", "slab", "tokio", "tokio-util", @@ -2269,13 +2336,19 @@ dependencies = [ "futures-sink", "futures-util", "http 1.1.0", - "indexmap", + "indexmap 2.2.5", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.13.2" @@ -2571,6 +2644,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.2.5" @@ -2750,18 +2833,45 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets 0.52.4", +] + [[package]] name = "libm" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libproc" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae9ea4b75e1a81675429dafe43441df1caea70081e82246a8cccf514884a88bb" +dependencies = [ + "bindgen", + "errno", + "libc", +] + [[package]] name = "libredox" version = "0.0.1" @@ -2804,6 +2914,15 @@ dependencies = [ "hashbrown 0.14.3", ] +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + [[package]] name = "md-5" version = "0.10.6" @@ -2820,12 +2939,88 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "metrics" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde3af1a009ed76a778cb84fdef9e7dbbdf5775ae3e4cc1f434a6a307f6f76c5" +dependencies = [ + "ahash", + "metrics-macros", + "portable-atomic", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d4fa7ce7c4862db464a37b0b31d89bca874562f034bd7993895572783d02950" +dependencies = [ + "base64 0.21.7", + "hyper 0.14.28", + "indexmap 1.9.3", + "ipnet", + "metrics", + "metrics-util", + "quanta", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-macros" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b4faf00617defe497754acde3024865bc143d44a86799b24e191ecff91354f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "metrics-process" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa2a67e2580fbeba4d5a96e659945981e700a383b4cea1432e0cfc18f58c5da" +dependencies = [ + "libproc", + "mach2", + "metrics", + "once_cell", + "procfs", + "rlimit", + "windows", +] + +[[package]] +name = "metrics-util" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "111cb375987443c3de8d503580b536f77dc8416d32db62d9456db5d93bd7ac47" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.13.2", + "metrics", + "num_cpus", + "quanta", + "sketches-ddsketch", +] + [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -2870,6 +3065,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-bigint" version = "0.4.4" @@ -3154,7 +3359,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 2.2.5", ] [[package]] @@ -3266,6 +3471,12 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + [[package]] name = "powerfmt" version = "0.2.0" @@ -3361,6 +3572,44 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "procfs" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" +dependencies = [ + "bitflags 2.4.2", + "hex", + "lazy_static", + "procfs-core", + "rustix", +] + +[[package]] +name = "procfs-core" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" +dependencies = [ + "bitflags 2.4.2", + "hex", +] + +[[package]] +name = "prometheus" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "memchr", + "parking_lot", + "protobuf", + "thiserror", +] + [[package]] name = "proptest" version = "1.4.0" @@ -3381,6 +3630,28 @@ dependencies = [ "unarray", ] +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" + +[[package]] +name = "quanta" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17e662a7a8291a865152364c20c7abc5e60486ab2001e8ec10b24862de0b9ab" +dependencies = [ + "crossbeam-utils", + "libc", + "mach2", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -3441,6 +3712,15 @@ dependencies = [ "rand_core", ] +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "rayon" version = "1.9.0" @@ -3642,6 +3922,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "rlimit" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3560f70f30a0f16d11d01ed078a07740fe6b489667abc7c7b029155d9f21c3d8" +dependencies = [ + "libc", +] + [[package]] name = "rlp" version = "0.5.2" @@ -3700,6 +3989,12 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hex" version = "2.1.0" @@ -4057,6 +4352,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -4094,6 +4395,12 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "sketches-ddsketch" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" + [[package]] name = "slab" version = "0.4.9" @@ -4312,6 +4619,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.58" @@ -4514,7 +4830,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap", + "indexmap 2.2.5", "toml_datetime", "winnow 0.5.40", ] @@ -4525,7 +4841,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap", + "indexmap 2.2.5", "serde", "serde_spanned", "toml_datetime", @@ -4608,6 +4924,20 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "trybuild" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33a5f13f11071020bb12de7a16b925d2d58636175c20c11dc5f96cb64bb6c9b3" +dependencies = [ + "glob", + "serde", + "serde_derive", + "serde_json", + "termcolor", + "toml", +] + [[package]] name = "tungstenite" version = "0.20.1" @@ -4889,6 +5219,25 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.4", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index 03a55d5a..0e88930a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "crates/chainio/clients/avsregistry/", "crates/metrics/collectors/rpc_calls/", "crates/services/avsregistry/", "crates/services/bls_aggregation/", +"crates/metrics/metrics-derive", "crates/services/operatorsinfo/", "crates/types/", "crates/metrics/", @@ -41,9 +42,14 @@ rustdoc.all = "warn" [workspace.dependencies] +metrics-exporter-prometheus = "0.12.0" ethers = "2.0.14" ark-ff = "0.4.0" eyre = "0.6.12" +syn = "2.0" +quote = "1.0" +metrics = "0.21.1" +once_cell = "1.17" reqwest = "0.12.4" reth = {git = "https://github.com/paradigmxyz/reth"} prometheus-client = "0.22.2" @@ -65,11 +71,14 @@ eigen-metrics-collectors-rpc-calls = {path = "crates/metrics/collectors/rpc_call eigen-services-avsregistry = {path = "crates/services/avsregistry"} eigen-services-bls_aggregation = {path = "crates/services/bls_aggregation"} eigen-services-operatorsinfo = {path = "crates/services/operatorsinfo"} +eigen-metrics-derive = {path = "crates/metrics/metrics-derive"} info-operator-service = {path = "examples/info-operator-service"} tokio = {version = "1.37.0" , features = ["test-util", "full","sync"] } futures-util = "0.3.30" thiserror = "1.0" tracing = "0.1.40" +hyper = "0.14.25" + #misc parking_lot = "0.12" diff --git a/crates/chainio/clients/eth/src/instrumented_client.rs b/crates/chainio/clients/eth/src/instrumented_client.rs index 81497409..e29c8c1f 100644 --- a/crates/chainio/clients/eth/src/instrumented_client.rs +++ b/crates/chainio/clients/eth/src/instrumented_client.rs @@ -1,4 +1,4 @@ -use eigen_metrics_collectors_rpc_calls::Collector as RpcCallsCollector; +use eigen_metrics_collectors_rpc_calls::RpcCalls as RpcCallsCollector; use ethers::providers::{Http, Middleware, Provider}; pub struct InstrumentedClient { diff --git a/crates/metrics/Cargo.toml b/crates/metrics/Cargo.toml index 28114ec7..074dfa5e 100644 --- a/crates/metrics/Cargo.toml +++ b/crates/metrics/Cargo.toml @@ -9,4 +9,18 @@ repository.workspace = true ark-bn254 = "0.4.0" ark-ff.workspace = true eigen-crypto-bls.workspace = true -eigen-crypto-bn254.workspace = true \ No newline at end of file +eigen-crypto-bn254.workspace = true +eigen-metrics-derive.workspace = true +metrics.workspace = true + +#prometheus +prometheus = "0.13.4" +metrics-exporter-prometheus.workspace = true +eigen-metrics-collectors-economic.workspace = true +reqwest = "0.12.4" +eyre.workspace = true + +#http +hyper.workspace = true + +tokio.workspace = true \ No newline at end of file diff --git a/crates/metrics/collectors/economic/Cargo.toml b/crates/metrics/collectors/economic/Cargo.toml index 5cc92b37..1545eebd 100644 --- a/crates/metrics/collectors/economic/Cargo.toml +++ b/crates/metrics/collectors/economic/Cargo.toml @@ -10,4 +10,7 @@ repository.workspace = true ethers.workspace = true eigen-client-elcontracts.workspace = true eigen-client-avsregistry.workspace = true -alloy-primitives.workspace = true \ No newline at end of file +alloy-primitives.workspace = true +eigen-metrics-derive.workspace = true +metrics.workspace = true +metrics-process = "=1.0.14" diff --git a/crates/metrics/collectors/economic/src/lib.rs b/crates/metrics/collectors/economic/src/lib.rs index f5e4ee36..86784749 100644 --- a/crates/metrics/collectors/economic/src/lib.rs +++ b/crates/metrics/collectors/economic/src/lib.rs @@ -1,36 +1,41 @@ -use alloy_primitives::{Address, U256}; -use eigen_client_avsregistry::reader::AvsRegistryChainReader; -use eigen_client_elcontracts::reader::ELChainReader; -use std::collections::HashMap; +use eigen_metrics_derive::Metrics; +use metrics::{Gauge, Key, Label}; -pub struct Collector { - elreader: ELChainReader, - avs_registry_reader: AvsRegistryChainReader, - operator_addr: Address, - operator_id: [u8; 32], - quorum_names: HashMap, +#[derive(Clone, Metrics)] +#[metrics(scope = "eigen.registeredstakes")] +pub struct RegisteredStakes { + #[metric( + rename = "eigen_registered_stakes", + describe = " A gauge with weighted delegation of delegated shares in delegation manager contract " + )] + registered_stake: Gauge, } -impl Collector { - pub fn new( - elreader: ELChainReader, - avs_registry_reader: AvsRegistryChainReader, - operator_addr: Address, - operator_id: [u8; 32], - quorum_names: HashMap, - ) -> Self { - Self { - elreader, - avs_registry_reader, - operator_addr, - operator_id, - quorum_names, - } +impl RegisteredStakes { + pub fn new() -> Self { + let gauge = Self { + registered_stake: metrics::register_gauge!("eigen_registered_stakes"), + }; + RegisteredStakes::describe(); + + gauge } - pub fn describe(&self) {} + pub fn set_stake(&self, quorum_number: &str, quorum_name: &str, value: f64) { + // Create the metric key with dynamic labels + let key = Key::from_parts( + "eigen_registered_stakes", + vec![ + Label::new("quorum_number", quorum_number.to_string()), + Label::new("quorum_name", quorum_name.to_string()), + ], + ); - pub fn init_operator_id(&self) {} + // Register or retrieve the gauge with the specified key and set the value + metrics::gauge!(key.to_string(), value); + } - pub fn collect(&self) {} + pub fn registered_stakes(&self) -> Gauge { + self.registered_stake.clone() + } } diff --git a/crates/metrics/collectors/rpc_calls/Cargo.toml b/crates/metrics/collectors/rpc_calls/Cargo.toml index 0cf21f6e..a18f96e0 100644 --- a/crates/metrics/collectors/rpc_calls/Cargo.toml +++ b/crates/metrics/collectors/rpc_calls/Cargo.toml @@ -6,4 +6,5 @@ rust-version.workspace = true repository.workspace = true [dependencies] -ethers.workspace = true \ No newline at end of file +eigen-metrics-derive.workspace = true +metrics.workspace = true \ No newline at end of file diff --git a/crates/metrics/collectors/rpc_calls/src/lib.rs b/crates/metrics/collectors/rpc_calls/src/lib.rs index 1204b97c..3abf7e8c 100644 --- a/crates/metrics/collectors/rpc_calls/src/lib.rs +++ b/crates/metrics/collectors/rpc_calls/src/lib.rs @@ -1 +1,74 @@ -pub struct Collector {} +use eigen_metrics_derive::Metrics; +use metrics::{Counter, Histogram, Key, Label}; + +#[derive(Clone, Metrics)] +#[metrics(scope = "eigen.rpcmetrics")] +pub struct RpcCalls { + #[metric( + rename = "eigen_rpc_request_duration_seconds", + describe = " Duration of json-rpc in seconds from Ethereum Execution client " + )] + rpc_request_duration_seconds: Histogram, + #[metric( + rename = "eigen_rpc_request_total", + describe = "Total of json-rpc requests from Ethereum Execution client " + )] + rpc_request_total: Counter, +} + +impl RpcCalls { + pub fn new() -> Self { + let rpc_calls = Self { + rpc_request_duration_seconds: metrics::register_histogram!( + "eigen_rpc_request_duration_seconds" + ), + rpc_request_total: metrics::register_counter!("eigen_rpc_request_total"), + }; + + RpcCalls::describe(); + + rpc_calls + } + + pub fn rpc_request_duration_seconds(&self) -> Histogram { + self.rpc_request_duration_seconds.clone() + } + + pub fn rpc_request_total(&self) -> Counter { + self.rpc_request_total.clone() + } + + pub fn set_rpc_request_duration_seconds( + &self, + method: &str, + client_version: &str, + duration: f64, + ) { + let key = Key::from_parts( + "eigen_rpc_request_duration_seconds", + vec![ + Label::new("method", method.to_string()), + Label::new("client_version", client_version.to_string()), + ], + ); + + metrics::histogram!(key.to_string(), duration); + } + + pub fn set_rpc_request_total( + &self, + method: &str, + client_version: &str, + rpc_request_total: u64, + ) { + let key = Key::from_parts( + "eigen_rpc_request_total", + vec![ + Label::new("method", method.to_string()), + Label::new("client_version", client_version.to_string()), + ], + ); + + metrics::counter!(key.to_string(), rpc_request_total); + } +} diff --git a/crates/metrics/metrics-derive/Cargo.toml b/crates/metrics/metrics-derive/Cargo.toml new file mode 100644 index 00000000..864ca4f5 --- /dev/null +++ b/crates/metrics/metrics-derive/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "eigen-metrics-derive" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +repository.workspace = true + + +[lib] +proc-macro = true + +[dependencies] + +#prometheus +prometheus = "0.13.4" + +proc-macro2 = "1.0" + +syn = { workspace = true, features = ["extra-traits"] } +quote.workspace = true +regex = "1.6.0" +once_cell.workspace = true + +[dev-dependencies] +metrics.workspace = true +trybuild = "1.0" \ No newline at end of file diff --git a/crates/metrics/metrics-derive/src/expand.rs b/crates/metrics/metrics-derive/src/expand.rs new file mode 100644 index 00000000..a9a7dfaa --- /dev/null +++ b/crates/metrics/metrics-derive/src/expand.rs @@ -0,0 +1,458 @@ +use once_cell::sync::Lazy; +use quote::{quote, ToTokens}; +use regex::Regex; +use syn::{ + punctuated::Punctuated, Attribute, Data, DeriveInput, Error, Expr, Field, Lit, LitBool, LitStr, + Meta, MetaNameValue, Result, Token, +}; + +use crate::{metric::Metric, with_attrs::WithAttrs}; + +/// Metric name regex according to Prometheus data model +/// +/// See +static METRIC_NAME_RE: Lazy = + Lazy::new(|| Regex::new(r"^[a-zA-Z_:.][a-zA-Z0-9_:.]*$").unwrap()); + +/// Supported metrics separators +const SUPPORTED_SEPARATORS: &[&str] = &[".", "_", ":"]; + +enum MetricField<'a> { + Included(Metric<'a>), + Skipped(&'a Field), +} + +impl<'a> MetricField<'a> { + fn field(&self) -> &'a Field { + match self { + MetricField::Included(Metric { field, .. }) | MetricField::Skipped(field) => field, + } + } +} + +pub(crate) fn derive(node: &DeriveInput) -> Result { + let ty = &node.ident; + let vis = &node.vis; + let ident_name = ty.to_string(); + + let metrics_attr = parse_metrics_attr(node)?; + let metric_fields = parse_metric_fields(node)?; + + let describe_doc = quote! { + /// Describe all exposed metrics. Internally calls `describe_*` macros from + /// the metrics crate according to the metric type. + /// + /// See + }; + let register_and_describe = match &metrics_attr.scope { + MetricsScope::Static(scope) => { + let (defaults, labeled_defaults, describes): (Vec<_>, Vec<_>, Vec<_>) = metric_fields + .iter() + .map(|metric| { + let field_name = &metric.field().ident; + match metric { + MetricField::Included(metric) => { + let metric_name = format!( + "{}{}{}", + scope.value(), + metrics_attr.separator(), + metric.name() + ); + let registrar = metric.register_stmt()?; + let describe = metric.describe_stmt()?; + let description = &metric.description; + Ok(( + quote! { + #field_name: #registrar(#metric_name), + }, + quote! { + #field_name: #registrar(#metric_name, labels.clone()), + }, + Some(quote! { + #describe(#metric_name, #description); + }), + )) + } + MetricField::Skipped(_) => Ok(( + quote! { + #field_name: Default::default(), + }, + quote! { + #field_name: Default::default(), + }, + None, + )), + } + }) + .collect::>>()? + .into_iter() + .fold((vec![], vec![], vec![]), |mut acc, x| { + acc.0.push(x.0); + acc.1.push(x.1); + if let Some(describe) = x.2 { + acc.2.push(describe); + } + acc + }); + + quote! { + impl Default for #ty { + fn default() -> Self { + #ty::describe(); + + Self { + #(#defaults)* + } + } + } + + impl #ty { + /// Create new instance of metrics with provided labels. + #vis fn new_with_labels(labels: impl metrics::IntoLabels + Clone) -> Self { + Self { + #(#labeled_defaults)* + } + } + + #describe_doc + #vis fn describe() { + #(#describes)* + } + } + } + } + MetricsScope::Dynamic => { + let (defaults, labeled_defaults, describes): (Vec<_>, Vec<_>, Vec<_>) = metric_fields + .iter() + .map(|metric| { + let field_name = &metric.field().ident; + match metric { + MetricField::Included(metric) => { + let name = metric.name(); + let separator = metrics_attr.separator(); + let metric_name = quote! { + format!("{}{}{}", scope, #separator, #name) + }; + + let registrar = metric.register_stmt()?; + let describe = metric.describe_stmt()?; + let description = &metric.description; + + Ok(( + quote! { + #field_name: #registrar(#metric_name), + }, + quote! { + #field_name: #registrar(#metric_name, labels.clone()), + }, + Some(quote! { + #describe(#metric_name, #description); + }), + )) + } + MetricField::Skipped(_) => Ok(( + quote! { + #field_name: Default::default(), + }, + quote! { + #field_name: Default::default(), + }, + None, + )), + } + }) + .collect::>>()? + .into_iter() + .fold((vec![], vec![], vec![]), |mut acc, x| { + acc.0.push(x.0); + acc.1.push(x.1); + if let Some(describe) = x.2 { + acc.2.push(describe); + } + acc + }); + + quote! { + impl #ty { + /// Create new instance of metrics with provided scope. + #vis fn new(scope: &str) -> Self { + #ty::describe(scope); + + Self { + #(#defaults)* + } + } + + /// Create new instance of metrics with provided labels. + #vis fn new_with_labels(scope: &str, labels: impl metrics::IntoLabels + Clone) -> Self { + Self { + #(#labeled_defaults)* + } + } + + #describe_doc + #vis fn describe(scope: &str) { + #(#describes)* + } + } + } + } + }; + Ok(quote! { + #register_and_describe + + impl std::fmt::Debug for #ty { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct(#ident_name).finish() + } + } + }) +} + +pub(crate) struct MetricsAttr { + pub(crate) scope: MetricsScope, + pub(crate) separator: Option, +} + +impl MetricsAttr { + const DEFAULT_SEPARATOR: &'static str = "."; + + fn separator(&self) -> String { + match &self.separator { + Some(sep) => sep.value(), + None => MetricsAttr::DEFAULT_SEPARATOR.to_owned(), + } + } +} + +pub(crate) enum MetricsScope { + Static(LitStr), + Dynamic, +} + +fn parse_metrics_attr(node: &DeriveInput) -> Result { + let metrics_attr = parse_single_required_attr(node, "metrics")?; + let parsed = + metrics_attr.parse_args_with(Punctuated::::parse_terminated)?; + let (mut scope, mut separator, mut dynamic) = (None, None, None); + for kv in parsed { + let lit = match kv.value { + Expr::Lit(ref expr) => &expr.lit, + _ => continue, + }; + if kv.path.is_ident("scope") { + if scope.is_some() { + return Err(Error::new_spanned(kv, "Duplicate `scope` value provided.")); + } + let scope_lit = parse_str_lit(lit)?; + validate_metric_name(&scope_lit)?; + scope = Some(scope_lit); + } else if kv.path.is_ident("separator") { + if separator.is_some() { + return Err(Error::new_spanned( + kv, + "Duplicate `separator` value provided.", + )); + } + let separator_lit = parse_str_lit(lit)?; + if !SUPPORTED_SEPARATORS.contains(&&*separator_lit.value()) { + return Err(Error::new_spanned( + kv, + format!( + "Unsupported `separator` value. Supported: {}.", + SUPPORTED_SEPARATORS + .iter() + .map(|sep| format!("`{sep}`")) + .collect::>() + .join(", ") + ), + )); + } + separator = Some(separator_lit); + } else if kv.path.is_ident("dynamic") { + if dynamic.is_some() { + return Err(Error::new_spanned(kv, "Duplicate `dynamic` flag provided.")); + } + dynamic = Some(parse_bool_lit(lit)?.value); + } else { + return Err(Error::new_spanned(kv, "Unsupported attribute entry.")); + } + } + + let scope = match (scope, dynamic) { + (Some(scope), None) | (Some(scope), Some(false)) => MetricsScope::Static(scope), + (None, Some(true)) => MetricsScope::Dynamic, + (Some(_), Some(_)) => { + return Err(Error::new_spanned( + node, + "`scope = ..` conflicts with `dynamic = true`.", + )) + } + _ => { + return Err(Error::new_spanned( + node, + "Either `scope = ..` or `dynamic = true` must be set.", + )) + } + }; + + Ok(MetricsAttr { scope, separator }) +} + +fn parse_metric_fields(node: &DeriveInput) -> Result>> { + let Data::Struct(ref data) = node.data else { + return Err(Error::new_spanned(node, "Only structs are supported.")); + }; + + let mut metrics = Vec::with_capacity(data.fields.len()); + for field in data.fields.iter() { + let (mut describe, mut rename, mut skip) = (None, None, false); + if let Some(metric_attr) = parse_single_attr(field, "metric")? { + let parsed = + metric_attr.parse_args_with(Punctuated::::parse_terminated)?; + for meta in parsed { + match meta { + Meta::Path(path) if path.is_ident("skip") => skip = true, + Meta::NameValue(kv) => { + let lit = match kv.value { + Expr::Lit(ref expr) => &expr.lit, + _ => continue, + }; + if kv.path.is_ident("describe") { + if describe.is_some() { + return Err(Error::new_spanned( + kv, + "Duplicate `describe` value provided.", + )); + } + describe = Some(parse_str_lit(lit)?); + } else if kv.path.is_ident("rename") { + if rename.is_some() { + return Err(Error::new_spanned( + kv, + "Duplicate `rename` value provided.", + )); + } + let rename_lit = parse_str_lit(lit)?; + validate_metric_name(&rename_lit)?; + rename = Some(rename_lit) + } else { + return Err(Error::new_spanned(kv, "Unsupported attribute entry.")); + } + } + _ => return Err(Error::new_spanned(meta, "Unsupported attribute entry.")), + } + } + } + + if skip { + metrics.push(MetricField::Skipped(field)); + continue; + } + + let description = match describe { + Some(lit_str) => lit_str.value(), + // Parse docs only if `describe` attribute was not provided + None => match parse_docs_to_string(field)? { + Some(docs_str) => docs_str, + None => { + return Err(Error::new_spanned( + field, + "Either doc comment or `describe = ..` must be set.", + )) + } + }, + }; + + metrics.push(MetricField::Included(Metric::new( + field, + description, + rename, + ))); + } + + Ok(metrics) +} + +fn validate_metric_name(name: &LitStr) -> Result<()> { + if METRIC_NAME_RE.is_match(&name.value()) { + Ok(()) + } else { + Err(Error::new_spanned( + name, + format!("Value must match regex {}", METRIC_NAME_RE.as_str()), + )) + } +} + +fn parse_single_attr<'a, T: WithAttrs + ToTokens>( + token: &'a T, + ident: &str, +) -> Result> { + let mut attr_iter = token.attrs().iter().filter(|a| a.path().is_ident(ident)); + if let Some(attr) = attr_iter.next() { + if let Some(next_attr) = attr_iter.next() { + Err(Error::new_spanned( + next_attr, + format!("Duplicate `#[{ident}(..)]` attribute provided."), + )) + } else { + Ok(Some(attr)) + } + } else { + Ok(None) + } +} + +fn parse_single_required_attr<'a, T: WithAttrs + ToTokens>( + token: &'a T, + ident: &str, +) -> Result<&'a Attribute> { + if let Some(attr) = parse_single_attr(token, ident)? { + Ok(attr) + } else { + Err(Error::new_spanned( + token, + format!("`#[{ident}(..)]` attribute must be provided."), + )) + } +} + +fn parse_docs_to_string(token: &T) -> Result> { + let mut doc_str = None; + for attr in token.attrs().iter() { + if let syn::Meta::NameValue(ref meta) = attr.meta { + if let Expr::Lit(ref lit) = meta.value { + if let Lit::Str(ref doc) = lit.lit { + let doc_value = doc.value().trim().to_string(); + doc_str = Some( + doc_str + .map(|prev_doc_value| format!("{prev_doc_value} {doc_value}")) + .unwrap_or(doc_value), + ); + } + } + } + } + Ok(doc_str) +} + +fn parse_str_lit(lit: &Lit) -> Result { + match lit { + Lit::Str(lit_str) => Ok(lit_str.to_owned()), + _ => Err(Error::new_spanned( + lit, + "Value **must** be a string literal.", + )), + } +} + +fn parse_bool_lit(lit: &Lit) -> Result { + match lit { + Lit::Bool(lit_bool) => Ok(lit_bool.to_owned()), + _ => Err(Error::new_spanned( + lit, + "Value **must** be a string literal.", + )), + } +} diff --git a/crates/metrics/metrics-derive/src/lib.rs b/crates/metrics/metrics-derive/src/lib.rs new file mode 100644 index 00000000..95d42abe --- /dev/null +++ b/crates/metrics/metrics-derive/src/lib.rs @@ -0,0 +1,131 @@ +use proc_macro::TokenStream; +use syn::{parse_macro_input, DeriveInput}; + +mod expand; +mod metric; +mod with_attrs; + +/// The [Metrics] derive macro instruments all of the struct fields and +/// creates a [Default] implementation for the struct registering all of +/// the metrics. +/// +/// Additionally, it creates a `describe` method on the struct, which +/// internally calls the describe statements for all metric fields. +/// +/// Sample usage: +/// ``` +/// use metrics::{Counter, Gauge, Histogram}; +/// use eigen_metrics_derive::Metrics; +/// +/// #[derive(Metrics)] +/// #[metrics(scope = "metrics_custom")] +/// pub struct CustomMetrics { +/// /// A gauge with doc comment description. +/// gauge: Gauge, +/// #[metric(rename = "second_gauge", describe = "A gauge with metric attribute description.")] +/// gauge2: Gauge, +/// /// Some doc comment +/// #[metric(describe = "Metric attribute description will be preferred over doc comment.")] +/// counter: Counter, +/// /// A renamed histogram. +/// #[metric(rename = "histogram")] +/// histo: Histogram, +/// } +/// ``` +/// +/// The example above will be expanded to: +/// ``` +/// pub struct CustomMetrics { +/// /// A gauge with doc comment description. +/// gauge: metrics::Gauge, +/// gauge2: metrics::Gauge, +/// /// Some doc comment +/// counter: metrics::Counter, +/// /// A renamed histogram. +/// histo: metrics::Histogram, +/// } +/// +/// impl Default for CustomMetrics { +/// fn default() -> Self { +/// Self { +/// gauge: metrics::register_gauge!("metrics_custom_gauge"), +/// gauge2: metrics::register_gauge!("metrics_custom_second_gauge"), +/// counter: metrics::register_counter!("metrics_custom_counter"), +/// histo: metrics::register_histogram!("metrics_custom_histogram"), +/// } +/// } +/// } +/// +/// impl CustomMetrics { +/// /// Describe all exposed metrics +/// pub fn describe() { +/// metrics::describe_gauge!( +/// "metrics_custom_gauge", +/// "A gauge with doc comment description." +/// ); +/// metrics::describe_gauge!( +/// "metrics_custom_second_gauge", +/// "A gauge with metric attribute description." +/// ); +/// metrics::describe_counter!( +/// "metrics_custom_counter", +/// "Metric attribute description will be preferred over doc comment." +/// ); +/// metrics::describe_histogram!("metrics_custom_histogram", "A renamed histogram."); +/// } +/// } +/// +/// impl std::fmt::Debug for CustomMetrics { +/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +/// f.debug_struct("CustomMetrics").finish() +/// } +/// } +/// ``` +/// +/// Similarly, you can derive metrics with "dynamic" scope, +/// meaning their scope can be set at the time of instantiation. +/// For example: +/// ``` +/// use eigen_metrics_derive::Metrics; +/// +/// #[derive(Metrics)] +/// #[metrics(dynamic = true)] +/// pub struct DynamicScopeMetrics { +/// /// A gauge with doc comment description. +/// gauge: metrics::Gauge, +/// } +/// ``` +/// +/// The example with dynamic scope will expand to +/// ``` +/// pub struct DynamicScopeMetrics { +/// /// A gauge with doc comment description. +/// gauge: metrics::Gauge, +/// } +/// +/// impl DynamicScopeMetrics { +/// pub fn new(scope: &str) -> Self { +/// Self { gauge: metrics::register_gauge!(format!("{}{}{}", scope, "_", "gauge")) } +/// } +/// +/// pub fn describe(scope: &str) { +/// metrics::describe_gauge!( +/// format!("{}{}{}", scope, "_", "gauge"), +/// "A gauge with doc comment description." +/// ); +/// } +/// } +/// +/// impl std::fmt::Debug for DynamicScopeMetrics { +/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +/// f.debug_struct("DynamicScopeMetrics").finish() +/// } +/// } +/// ``` +#[proc_macro_derive(Metrics, attributes(metrics, metric))] +pub fn derive_metrics(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + expand::derive(&input) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} diff --git a/crates/metrics/metrics-derive/src/metric.rs b/crates/metrics/metrics-derive/src/metric.rs new file mode 100644 index 00000000..c0e635d2 --- /dev/null +++ b/crates/metrics/metrics-derive/src/metric.rs @@ -0,0 +1,74 @@ +use quote::quote; +use syn::{Error, Field, LitStr, Result, Type}; + +const COUNTER_TY: &str = "Counter"; +const HISTOGRAM_TY: &str = "Histogram"; +const GAUGE_TY: &str = "Gauge"; + +pub(crate) struct Metric<'a> { + pub(crate) field: &'a Field, + pub(crate) description: String, + rename: Option, +} + +impl<'a> Metric<'a> { + pub(crate) fn new(field: &'a Field, description: String, rename: Option) -> Self { + Self { + field, + description, + rename, + } + } + + pub(crate) fn name(&self) -> String { + match self.rename.as_ref() { + Some(name) => name.value(), + None => self + .field + .ident + .as_ref() + .map(ToString::to_string) + .unwrap_or_default(), + } + } + + pub(crate) fn register_stmt(&self) -> Result { + if let Type::Path(ref path_ty) = self.field.ty { + if let Some(last) = path_ty.path.segments.last() { + let registrar = match last.ident.to_string().as_str() { + COUNTER_TY => quote! { metrics::register_counter! }, + HISTOGRAM_TY => quote! { metrics::register_histogram! }, + GAUGE_TY => quote! { metrics::register_gauge! }, + _ => return Err(Error::new_spanned(path_ty, "Unsupported metric type")), + }; + + return Ok(quote! { #registrar }); + } + } + + Err(Error::new_spanned( + &self.field.ty, + "Unsupported metric type", + )) + } + + pub(crate) fn describe_stmt(&self) -> Result { + if let Type::Path(ref path_ty) = self.field.ty { + if let Some(last) = path_ty.path.segments.last() { + let descriptor = match last.ident.to_string().as_str() { + COUNTER_TY => quote! { metrics::describe_counter! }, + HISTOGRAM_TY => quote! { metrics::describe_histogram! }, + GAUGE_TY => quote! { metrics::describe_gauge! }, + _ => return Err(Error::new_spanned(path_ty, "Unsupported metric type")), + }; + + return Ok(quote! { #descriptor }); + } + } + + Err(Error::new_spanned( + &self.field.ty, + "Unsupported metric type", + )) + } +} diff --git a/crates/metrics/metrics-derive/src/with_attrs.rs b/crates/metrics/metrics-derive/src/with_attrs.rs new file mode 100644 index 00000000..9095d996 --- /dev/null +++ b/crates/metrics/metrics-derive/src/with_attrs.rs @@ -0,0 +1,17 @@ +use syn::{Attribute, DeriveInput, Field}; + +pub(crate) trait WithAttrs { + fn attrs(&self) -> &[Attribute]; +} + +impl WithAttrs for DeriveInput { + fn attrs(&self) -> &[Attribute] { + &self.attrs + } +} + +impl WithAttrs for Field { + fn attrs(&self) -> &[Attribute] { + &self.attrs + } +} diff --git a/crates/metrics/src/eigenmetrics.rs b/crates/metrics/src/eigenmetrics.rs index 7ca279e2..e15a0acc 100644 --- a/crates/metrics/src/eigenmetrics.rs +++ b/crates/metrics/src/eigenmetrics.rs @@ -1 +1,28 @@ -pub struct EigenMetrics {} +use eigen_metrics_derive::Metrics; +use metrics::{Counter, Gauge}; +/// TODO implement other metrics also . For now only perfomrmance is supported. +#[derive(Clone, Metrics)] +#[metrics(scope = "eigenmetrics.performancemetrics")] +pub struct EigenMetrics { + /// performance score + #[metric( + rename = "eigen_performance_score", + describe = " A gauge with performance score " + )] + performance_score: Gauge, +} + +impl EigenMetrics { + pub fn new() -> Self { + let gauge = Self { + performance_score: metrics::register_gauge!("eigen_performance_score"), + }; + EigenMetrics::describe(); + gauge.performance_score.set(100 as f64); + gauge + } + + pub fn performance_score(&self) -> Gauge { + return self.performance_score.clone(); + } +} diff --git a/crates/metrics/src/lib.rs b/crates/metrics/src/lib.rs index f8b30e8b..77c94c9f 100644 --- a/crates/metrics/src/lib.rs +++ b/crates/metrics/src/lib.rs @@ -1,2 +1,3 @@ pub mod eigenmetrics; pub mod noopmetrics; +pub mod prometheus; diff --git a/crates/metrics/src/prometheus.rs b/crates/metrics/src/prometheus.rs new file mode 100644 index 00000000..429150f6 --- /dev/null +++ b/crates/metrics/src/prometheus.rs @@ -0,0 +1,91 @@ +use crate::eigenmetrics::EigenMetrics; +use eigen_metrics_collectors_economic::RegisteredStakes; +use eyre::Result; +use hyper::{ + body::Body, + service::{make_service_fn, service_fn}, + Request, Response, Server, +}; +use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle}; +use std::{convert::Infallible, net::SocketAddr}; +use tokio::runtime::Runtime; +use tokio::time::sleep; +use tokio::time::Duration; + +fn init_registry() -> PrometheusHandle { + let recorder = PrometheusBuilder::new().build_recorder(); + let handle = recorder.handle(); + let boxed_recorder = Box::new(recorder); + let static_recorder: &'static dyn metrics::Recorder = Box::leak(boxed_recorder); + metrics::set_recorder(static_recorder).expect("failed to set metrics recorder"); + handle +} + +async fn serve_metrics(addr: SocketAddr, handle: PrometheusHandle) -> eyre::Result<()> { + let make_svc = make_service_fn(move |_| { + let handle = handle.clone(); + + async move { + Ok::<_, Infallible>(service_fn(move |_: Request| { + let metrics = handle.render(); + async move { Ok::<_, Infallible>(Response::new(Body::from(metrics))) } + })) + } + }); + + let server = Server::bind(&addr).serve(make_svc); + server.await?; + Ok(()) +} + +#[cfg(test)] + +mod tests { + + use super::*; + + #[tokio::test] + async fn test_prometheus_server() { + let socket: SocketAddr = "127.0.0.1:9091".parse().unwrap(); + let handle = init_registry(); + + // Initialize EigenMetrics + let metrics = EigenMetrics::new(); + let registered_metrics = RegisteredStakes::new(); + // Run the metrics server in a background task + let server_handle = tokio::spawn(async move { + serve_metrics(socket, handle).await.unwrap(); + }); + + sleep(Duration::from_secs(1)).await; + + async fn get_metrics_body(client: &reqwest::Client, url: &str) -> String { + let resp = client.get(url).send().await.unwrap(); + resp.text().await.unwrap() + } + let client = reqwest::Client::new(); + let resp = client + .get("http://127.0.0.1:9091/metrics") + .send() + .await + .unwrap(); + + let mut body = resp.text().await.unwrap(); + println!("body :{:?}", body); + assert!(body.contains("eigen_performance_score 100")); + + metrics.performance_score().set(80.0); + registered_metrics.set_stake("4th", "hello Eigen", 8.0); + + sleep(Duration::from_secs(1)).await; + + body = get_metrics_body(&client, "http://127.0.0.1:9091/metrics").await; + assert!(body.contains("eigen_performance_score 80")); + assert!(body.contains( + "Key_eigen_registered_stakes___quorum_number___4th__quorum_name___hello_Eigen__ 8" + )); + + // Shutdown the server + server_handle.abort(); + } +} diff --git a/crates/utils/src/binding.rs b/crates/utils/src/binding.rs index 80a909c7..fbdefe7c 100644 --- a/crates/utils/src/binding.rs +++ b/crates/utils/src/binding.rs @@ -91,4 +91,3 @@ sol!( ECDSAStakeRegistry, "../../crates/contracts/bindings/utils/json/ECDSAStakeRegistry.json" ); -