From 3d10c404be6d0eac6f69dd80617b569cef266629 Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Wed, 29 May 2024 00:00:00 +0000 Subject: [PATCH 01/12] =?UTF-8?q?pd:=20=F0=9F=92=A4=20a=20sleep=20worker,?= =?UTF-8?q?=20tracking=20scheduler=20latency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit see the module-level docs of `pd::metrics::sleep_worker`. this introduces a submodule to `pd::metrics`, which observes tokio scheduler latency and records it for the Prometheus exporter. the core idea in this worker is that it will invoke `tokio::time::sleep(..)` to put the task to sleep for one second. upon being woken up by the scheduler, it will check the *new* wall-clock time and calculate the actual amount of time it spent waiting. if this took longer than expected, it increments the `pd_async_sleep_drift_milliseconds` counter by the observed latency, measured in milliseconds. as the module-level docs note, this is a very useful tool to identify when the runtime is being disrupted by blocking I/O or other expensive computation. pd: šŸšŖ `metrics` module can be `pub` this will make it slightly nicer to expose facilities for telemetry in the `pd` binary, without clouding the top-level namespace. (cherry picked from commit 10421b80e1ccb82de3ce3b7368450fc3c0f0ad34) --- crates/bin/pd/src/lib.rs | 2 +- crates/bin/pd/src/main.rs | 1 + crates/bin/pd/src/metrics.rs | 3 ++ crates/bin/pd/src/metrics/sleep_worker.rs | 65 +++++++++++++++++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 crates/bin/pd/src/metrics/sleep_worker.rs diff --git a/crates/bin/pd/src/lib.rs b/crates/bin/pd/src/lib.rs index ce66736731..7412b17875 100644 --- a/crates/bin/pd/src/lib.rs +++ b/crates/bin/pd/src/lib.rs @@ -5,7 +5,7 @@ // Requires nightly. #![cfg_attr(docsrs, feature(doc_auto_cfg))] -mod metrics; +pub mod metrics; pub mod cli; pub mod migrate; diff --git a/crates/bin/pd/src/main.rs b/crates/bin/pd/src/main.rs index cf954a9a2f..7597ba762f 100644 --- a/crates/bin/pd/src/main.rs +++ b/crates/bin/pd/src/main.rs @@ -195,6 +195,7 @@ async fn main() -> anyhow::Result<()> { // register pd's metrics with the exporter. tokio::spawn(exporter); pd::register_metrics(); + tokio::spawn(pd::metrics::sleep_worker::run()); // We error out if a service errors, rather than keep running. // A special attempt is made to detect whether binding to target socket failed; diff --git a/crates/bin/pd/src/metrics.rs b/crates/bin/pd/src/metrics.rs index 44c0a4594d..ee5a419343 100644 --- a/crates/bin/pd/src/metrics.rs +++ b/crates/bin/pd/src/metrics.rs @@ -14,6 +14,8 @@ #[allow(unused_imports)] // It is okay if this reĆ«xport isn't used, see above. pub use metrics::*; +pub mod sleep_worker; + /// Registers all metrics used by this crate. /// /// For this implementation, in the `pd` crate, we also call the `register_metrics()` @@ -21,4 +23,5 @@ pub use metrics::*; pub fn register_metrics() { // This will register metrics for all components. penumbra_app::register_metrics(); + self::sleep_worker::register_metrics(); } diff --git a/crates/bin/pd/src/metrics/sleep_worker.rs b/crates/bin/pd/src/metrics/sleep_worker.rs new file mode 100644 index 0000000000..c0834bbd47 --- /dev/null +++ b/crates/bin/pd/src/metrics/sleep_worker.rs @@ -0,0 +1,65 @@ +//! A sleep worker. +//! +//! ### Overview +//! +//! This submodule defines a metric, and an accompanying worker task, for use in measuring +//! scheduler latency in the tokio runtime. This worker will repeatedly sleep for one second, and +//! then observe the amount of time it *actually* spent waiting to be woken up. This is useful for +//! detecting when the asynchronous runtime is being disrupted by blocking I/O, or other expensive +//! non-coƶperative computation. +//! +//! Use [`register_metrics()`] to register the [`SLEEP_DRIFT`] metric with an exporter, and spawn +//! the worker onto a runtime by calling [`run()`]. + +use { + super::*, + std::time::{Duration, Instant}, + tokio::time::sleep, +}; + +pub const SLEEP_DRIFT: &str = "pd_async_sleep_drift_microseconds"; + +const ONE_SECOND: Duration = Duration::from_secs(1); +const ONE_SECOND_US: u128 = ONE_SECOND.as_micros(); + +pub fn register_metrics() { + describe_counter!( + SLEEP_DRIFT, + Unit::Microseconds, + "Tracks drift in the async runtime's timer, in microseconds." + ); +} + +/// Run the sleep worker. +/// +/// This function will never return. +pub async fn run() -> std::convert::Infallible { + let counter = counter!(SLEEP_DRIFT); + + loop { + // Ask the async runtime to pause this task for one second, and then observe the amount of + // microseconds we were actually suspended. + let start = Instant::now(); + sleep(ONE_SECOND).await; + let end = Instant::now(); + let actual = end.duration_since(start).as_micros(); + + // Find the difference between the observed sleep duration and our expected duration. + let drift: u64 = actual + .saturating_sub(ONE_SECOND_US) + .try_into() + .unwrap_or_else(|error| { + // In the unlikely event that the number of microseconds we waited can't fit into + // a u64, round down to u64::MAX. This is lossy, but will still indicate that + // there is a severe issue with the runtime. + tracing::error!(?error, %actual, "failed to convert timer drift into a u64"); + u64::MAX + }); + + // If there was scheduler drift, increment the counter. + match drift { + 0 => continue, + n => counter.increment(n), + } + } +} From 34f1755f49cc0bbbbf930491c991f6ab3a47cc7b Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Wed, 29 May 2024 00:00:00 +0000 Subject: [PATCH 02/12] =?UTF-8?q?dex:=20=E2=98=94=20`set=5Fbuckets=5Ffor?= =?UTF-8?q?=5Fdex=5Fmetrics()`=20builder=20extension?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit this commit moves the logic responsible for configuring dex metrics into the dex's metrics module. there are some lurking footguns here, so we can avoid easily forgotten non-local reasoning and move the regex/prefix logic next to the metrics. (cherry picked from commit da1d31efe9968c75aaf947d5b444aa86a4df7ef2) --- Cargo.lock | 1 + Cargo.toml | 1 + crates/bin/pd/Cargo.toml | 2 +- crates/bin/pd/src/main.rs | 6 ++---- crates/core/component/dex/Cargo.toml | 2 ++ .../component/dex/src/component/metrics.rs | 20 +++++++++++++++++++ 6 files changed, 27 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6d80bb14d6..2e83c17f4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5024,6 +5024,7 @@ dependencies = [ "im", "itertools 0.11.0", "metrics", + "metrics-exporter-prometheus", "once_cell", "parking_lot", "pbjson-types", diff --git a/Cargo.toml b/Cargo.toml index 496633d81c..4b73297ef2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -156,6 +156,7 @@ im = { version = "^15.1.0" } indicatif = { version = "0.16" } jmt = { version = "0.10", features = ["migration"] } metrics = { version = "0.22" } +metrics-exporter-prometheus = { version = "0.13", features = ["http-listener"] } metrics-tracing-context = { version = "0.15" } num-bigint = { version = "0.4" } num-traits = { default-features = false, version = "0.2.15" } diff --git a/crates/bin/pd/Cargo.toml b/crates/bin/pd/Cargo.toml index 6c8e12451f..70a161c1f0 100644 --- a/crates/bin/pd/Cargo.toml +++ b/crates/bin/pd/Cargo.toml @@ -59,7 +59,7 @@ ibc-types = { workspace = true, default-features = true } ics23 = { workspace = true } jmt = { workspace = true } metrics = { workspace = true } -metrics-exporter-prometheus = { version = "0.13", features = ["http-listener"] } +metrics-exporter-prometheus = { workspace = true } metrics-tracing-context = { workspace = true } metrics-util = "0.16.2" mime_guess = "2" diff --git a/crates/bin/pd/src/main.rs b/crates/bin/pd/src/main.rs index 7597ba762f..6280789f52 100644 --- a/crates/bin/pd/src/main.rs +++ b/crates/bin/pd/src/main.rs @@ -166,14 +166,12 @@ async fn main() -> anyhow::Result<()> { }; // Configure a Prometheus recorder and exporter. + use penumbra_dex::component::metrics::PrometheusBuilderExt; let (recorder, exporter) = PrometheusBuilder::new() .with_http_listener(metrics_bind) // Set explicit buckets so that Prometheus endpoint emits true histograms, rather // than the default distribution type summaries, for time-series data. - .set_buckets_for_metric( - metrics_exporter_prometheus::Matcher::Prefix("penumbra_dex_".to_string()), - penumbra_dex::component::metrics::DEX_BUCKETS, - )? + .set_buckets_for_dex_metrics()? .build() .map_err(|e| { let msg = format!( diff --git a/crates/core/component/dex/Cargo.toml b/crates/core/component/dex/Cargo.toml index 579582ad21..d4eb923904 100644 --- a/crates/core/component/dex/Cargo.toml +++ b/crates/core/component/dex/Cargo.toml @@ -7,6 +7,7 @@ edition = {workspace = true} component = [ "cnidarium-component", "cnidarium", + "metrics-exporter-prometheus", "penumbra-proto/cnidarium", "penumbra-shielded-pool/component", "penumbra-fee/component", @@ -49,6 +50,7 @@ futures = {workspace = true} hex = {workspace = true} im = {workspace = true} metrics = {workspace = true} +metrics-exporter-prometheus = {workspace = true, optional = true} once_cell = {workspace = true} parking_lot = {workspace = true} pbjson-types = {workspace = true} diff --git a/crates/core/component/dex/src/component/metrics.rs b/crates/core/component/dex/src/component/metrics.rs index d7a7c8b7a0..7e26a2cfa9 100644 --- a/crates/core/component/dex/src/component/metrics.rs +++ b/crates/core/component/dex/src/component/metrics.rs @@ -71,3 +71,23 @@ pub const DEX_ARB_DURATION: &str = "penumbra_dex_arb_duration_seconds"; pub const DEX_BATCH_DURATION: &str = "penumbra_dex_batch_duration_seconds"; pub const DEX_RPC_SIMULATE_TRADE_DURATION: &str = "penumbra_dex_rpc_simulate_trade_duration_seconds"; + +/// An extension trait providing DEX-related interfaces for [`PrometheusBuilder`]. +/// +/// [builder]: metrics_exporter_prometheus::PrometheusBuilder +pub trait PrometheusBuilderExt +where + Self: Sized, +{ + /// Configure buckets for histogram metrics. + fn set_buckets_for_dex_metrics(self) -> Result; +} + +impl PrometheusBuilderExt for metrics_exporter_prometheus::PrometheusBuilder { + fn set_buckets_for_dex_metrics(self) -> Result { + self.set_buckets_for_metric( + metrics_exporter_prometheus::Matcher::Prefix("penumbra_dex_".to_string()), + DEX_BUCKETS, + ) + } +} From 0bf6a516f28c5475383ba0d0e183d3eab1fa1623 Mon Sep 17 00:00:00 2001 From: Conor Schaefer Date: Mon, 17 Jun 2024 15:29:43 -0700 Subject: [PATCH 03/12] metrics: add os-level cpu stats to pd Pulls in the `metrics-process` crate [0] to bolt on CPU usage and other OS-level info to the metrics emitted by pd. Doing so will support a better first-run experience for node operators who lack a comprehensive pre-existing setup for monitoring node health, so they can get a sense of how much load pd is under. Adds a cursory first-pass on a new dashboard, but will need to follow up on that once the new metrics actually land on hosts that are receiving load. Also included a reference to metrics addd in #4581, as a treat. [0] https://docs.rs/metrics-process/2.0.0/metrics_process/index.html (cherry picked from commit 30456453a37e67f214e990fc6ef4ef91ac231531) --- Cargo.lock | 204 ++++++++++-- crates/bin/pd/Cargo.toml | 1 + crates/bin/pd/src/main.rs | 1 + crates/bin/pd/src/metrics.rs | 2 + crates/bin/pd/src/metrics/cpu_worker.rs | 34 ++ .../{Penumbra.json => pd-performance.json} | 301 +++++------------- 6 files changed, 294 insertions(+), 249 deletions(-) create mode 100644 crates/bin/pd/src/metrics/cpu_worker.rs rename deployments/config/grafana/dashboards/{Penumbra.json => pd-performance.json} (62%) diff --git a/Cargo.lock b/Cargo.lock index 2e83c17f4c..70a5da8ba6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -959,6 +959,26 @@ dependencies = [ "syn 2.0.51", ] +[[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.11.0", + "lazy_static", + "lazycell", + "proc-macro2 1.0.78", + "quote 1.0.35", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.51", +] + [[package]] name = "bip32" version = "0.5.1" @@ -1280,7 +1300,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.3", + "windows-targets 0.52.5", ] [[package]] @@ -3036,7 +3056,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.52.0", ] [[package]] @@ -3676,6 +3696,17 @@ 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 0.69.4", + "errno", + "libc", +] + [[package]] name = "libredox" version = "0.0.1" @@ -3704,7 +3735,7 @@ version = "0.11.0+8.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3386f101bcb4bd252d8e9d2fb41ec3b0862a15a62b478c355b2982efa469e3e" dependencies = [ - "bindgen", + "bindgen 0.65.1", "bzip2-sys", "cc", "glob", @@ -3789,6 +3820,15 @@ dependencies = [ "libc", ] +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + [[package]] name = "matchers" version = "0.0.1" @@ -3870,6 +3910,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "metrics-process" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7d8f5027620bf43b86e2c8144beea1e4323aec39241f5eae59dee54f79c6a29" +dependencies = [ + "libproc", + "mach2", + "metrics", + "once_cell", + "procfs", + "rlimit", + "windows", +] + [[package]] name = "metrics-tracing-context" version = "0.15.0" @@ -4554,6 +4609,7 @@ dependencies = [ "jmt", "metrics", "metrics-exporter-prometheus", + "metrics-process", "metrics-tracing-context", "metrics-util", "mime_guess", @@ -6303,6 +6359,29 @@ 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 0.38.31", +] + +[[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 = "proptest" version = "1.4.0" @@ -6793,6 +6872,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" +[[package]] +name = "rlimit" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3560f70f30a0f16d11d01ed078a07740fe6b489667abc7c7b029155d9f21c3d8" +dependencies = [ + "libc", +] + [[package]] name = "rocksdb" version = "0.21.0" @@ -8929,13 +9017,66 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132" +dependencies = [ + "windows-core 0.56.0", + "windows-targets 0.52.5", +] + [[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.3", + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-core" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-implement" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" +dependencies = [ + "proc-macro2 1.0.78", + "quote 1.0.35", + "syn 2.0.51", +] + +[[package]] +name = "windows-interface" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" +dependencies = [ + "proc-macro2 1.0.78", + "quote 1.0.35", + "syn 2.0.51", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.5", ] [[package]] @@ -8953,7 +9094,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.3", + "windows-targets 0.52.5", ] [[package]] @@ -8973,17 +9114,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.3", - "windows_aarch64_msvc 0.52.3", - "windows_i686_gnu 0.52.3", - "windows_i686_msvc 0.52.3", - "windows_x86_64_gnu 0.52.3", - "windows_x86_64_gnullvm 0.52.3", - "windows_x86_64_msvc 0.52.3", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -8994,9 +9136,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -9006,9 +9148,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -9018,9 +9160,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.3" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -9030,9 +9178,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -9042,9 +9190,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -9054,9 +9202,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -9066,9 +9214,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" diff --git a/crates/bin/pd/Cargo.toml b/crates/bin/pd/Cargo.toml index 70a161c1f0..99db79d89d 100644 --- a/crates/bin/pd/Cargo.toml +++ b/crates/bin/pd/Cargo.toml @@ -60,6 +60,7 @@ ics23 = { workspace = true } jmt = { workspace = true } metrics = { workspace = true } metrics-exporter-prometheus = { workspace = true } +metrics-process = "2.0.0" metrics-tracing-context = { workspace = true } metrics-util = "0.16.2" mime_guess = "2" diff --git a/crates/bin/pd/src/main.rs b/crates/bin/pd/src/main.rs index 6280789f52..8c37cfe3b3 100644 --- a/crates/bin/pd/src/main.rs +++ b/crates/bin/pd/src/main.rs @@ -194,6 +194,7 @@ async fn main() -> anyhow::Result<()> { tokio::spawn(exporter); pd::register_metrics(); tokio::spawn(pd::metrics::sleep_worker::run()); + tokio::spawn(pd::metrics::cpu_worker::run()); // We error out if a service errors, rather than keep running. // A special attempt is made to detect whether binding to target socket failed; diff --git a/crates/bin/pd/src/metrics.rs b/crates/bin/pd/src/metrics.rs index ee5a419343..cfd7016a42 100644 --- a/crates/bin/pd/src/metrics.rs +++ b/crates/bin/pd/src/metrics.rs @@ -14,6 +14,7 @@ #[allow(unused_imports)] // It is okay if this reĆ«xport isn't used, see above. pub use metrics::*; +pub mod cpu_worker; pub mod sleep_worker; /// Registers all metrics used by this crate. @@ -24,4 +25,5 @@ pub fn register_metrics() { // This will register metrics for all components. penumbra_app::register_metrics(); self::sleep_worker::register_metrics(); + self::cpu_worker::register_metrics(); } diff --git a/crates/bin/pd/src/metrics/cpu_worker.rs b/crates/bin/pd/src/metrics/cpu_worker.rs new file mode 100644 index 0000000000..1e5d5af8cf --- /dev/null +++ b/crates/bin/pd/src/metrics/cpu_worker.rs @@ -0,0 +1,34 @@ +//! A metrics-focused worker for gathering CPU load and other system stats. +//! +//! ### Overview +//! +//! This submodule provides a worker that wraps the [metrics-process] logic to export OS-level +//! runtime information about `pd`. + +use std::time::Duration; +use tokio::time::sleep; + +use metrics_process::Collector; + +/// The time to sleep between polling the OS for process info about `pd`. +const SLEEP_DURATION: Duration = Duration::from_secs(2); +/// The string prepended to all metrics emitted by [metrics-process]. +const METRICS_PREFIX: &str = "pd_"; + +pub fn register_metrics() { + // Call `describe()` method to register help string. + let collector = Collector::new(METRICS_PREFIX); + collector.describe(); +} + +/// Run the cpu worker. +/// +/// This function will never return. +pub async fn run() -> std::convert::Infallible { + let collector = Collector::new(METRICS_PREFIX); + loop { + // Periodically call `collect()` method to update information. + collector.collect(); + sleep(SLEEP_DURATION).await; + } +} diff --git a/deployments/config/grafana/dashboards/Penumbra.json b/deployments/config/grafana/dashboards/pd-performance.json similarity index 62% rename from deployments/config/grafana/dashboards/Penumbra.json rename to deployments/config/grafana/dashboards/pd-performance.json index fede433063..d6b39324ca 100644 --- a/deployments/config/grafana/dashboards/Penumbra.json +++ b/deployments/config/grafana/dashboards/pd-performance.json @@ -1,4 +1,35 @@ { + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "Prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "10.1.5" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], "annotations": { "list": [ { @@ -21,9 +52,11 @@ } ] }, + "description": "Metrics specific to how pd performs under load.", "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, + "id": null, "links": [], "liveNow": false, "panels": [ @@ -32,6 +65,7 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "description": "The amount of CPU seconds used by pd, as rate in 1m intervals. How to represent as percentage of total available compute?", "fieldConfig": { "defaults": { "color": { @@ -51,6 +85,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -80,8 +115,7 @@ "value": 80 } ] - }, - "unit": "s" + } }, "overrides": [] }, @@ -91,7 +125,7 @@ "x": 0, "y": 0 }, - "id": 14, + "id": 17, "options": { "legend": { "calcs": [], @@ -111,13 +145,14 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "rate(cnidarium_get_raw_duration_seconds_sum[5m]) / rate(cnidarium_get_raw_duration_seconds_count[5m])", - "legendFormat": "__auto", + "expr": "rate(pd_process_cpu_seconds_total[$__rate_interval])", + "instant": false, + "legendFormat": "{{instance}}", "range": true, "refId": "A" } ], - "title": "Storage lookups, consensus", + "title": "pd CPU usage", "type": "timeseries" }, { @@ -125,6 +160,7 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "description": "Tracks drift in the async runtime's timer, in microseconds.", "fieldConfig": { "defaults": { "color": { @@ -144,6 +180,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -174,7 +211,7 @@ } ] }, - "unit": "s" + "unit": "Āµs" }, "overrides": [] }, @@ -184,7 +221,7 @@ "x": 12, "y": 0 }, - "id": 16, + "id": 19, "options": { "legend": { "calcs": [], @@ -204,13 +241,14 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "rate(cnidarium_nonverifiable_get_raw_duration_seconds_sum[5m]) / rate(cnidarium_nonverifiable_get_raw_duration_seconds_count[5m])", - "legendFormat": "__auto", + "expr": "rate(pd_async_sleep_drift_microseconds[$__rate_interval])", + "instant": false, + "legendFormat": "{{instance}}", "range": true, "refId": "A" } ], - "title": "Storage lookups, non-consensus", + "title": "pd async sleep drift", "type": "timeseries" }, { @@ -218,6 +256,7 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "description": "How much RAM pd is using", "fieldConfig": { "defaults": { "color": { @@ -237,6 +276,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -266,7 +306,8 @@ "value": 80 } ] - } + }, + "unit": "bytes" }, "overrides": [] }, @@ -276,7 +317,7 @@ "x": 0, "y": 8 }, - "id": 12, + "id": 18, "options": { "legend": { "calcs": [], @@ -295,11 +336,15 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "expr": "sum(penumbra_stake_missed_blocks) by (identity_key)", + "editorMode": "code", + "expr": "pd_process_resident_memory_bytes", + "instant": false, + "legendFormat": "{{instance}}", + "range": true, "refId": "A" } ], - "title": "Validator Missed Blocks", + "title": "pd memory usage", "type": "timeseries" }, { @@ -307,6 +352,7 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "description": "Tower ABCI uses a lot of filehandles, and defaults of 1024 are often too low. Track actual utilization.", "fieldConfig": { "defaults": { "color": { @@ -326,6 +372,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -353,203 +400,14 @@ { "color": "red", "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 16 - }, - "id": 10, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "exemplar": true, - "expr": "rate(penumbra_pd_mempool_checktx_total{code=\"0\", kind=\"new\"}[5m])", - "interval": "", - "legendFormat": "", - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "exemplar": true, - "expr": "rate(penumbra_pd_mempool_checktx_total{code=\"1\", kind=\"new\"}[5m])", - "hide": false, - "interval": "", - "legendFormat": "", - "refId": "B" - } - ], - "title": "Transaction Rates", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null }, { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 24 - }, - "id": 8, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.4.7", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "exemplar": true, - "expr": "cnidarium_tct_size_bytes{}", - "interval": "", - "legendFormat": "TCT Size (bytes)", - "refId": "A" - } - ], - "title": "Serialized TCT Size", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null + "color": "#EAB839", + "value": 90 }, { - "color": "red", - "value": 80 + "color": "#6ED0E0", + "value": 1024 } ] } @@ -560,9 +418,9 @@ "h": 8, "w": 12, "x": 12, - "y": 16 + "y": 8 }, - "id": 4, + "id": 20, "options": { "legend": { "calcs": [], @@ -581,19 +439,20 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "exemplar": true, - "expr": "penumbra_pd_mempool_checktx_total", - "interval": "", - "legendFormat": "", + "editorMode": "code", + "expr": "pd_process_open_fds", + "instant": false, + "legendFormat": "{{instance}}", + "range": true, "refId": "A" } ], - "title": "Penumbra CheckTX", + "title": "pd filehandle utilization", "type": "timeseries" } ], - "refresh": false, - "schemaVersion": 37, + "refresh": "", + "schemaVersion": 38, "style": "dark", "tags": [], "templating": { @@ -602,7 +461,7 @@ "current": { "selected": false, "text": "Prometheus", - "value": "Prometheus" + "value": "PBFA97CFB590B2093" }, "hide": 0, "includeAll": false, @@ -624,8 +483,8 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Penumbra", - "uid": "YT0tG3X7z", - "version": 1, + "title": "pd performance", + "uid": "fcdd2dc8-1357-4f8c-b23c-36bcd3914435", + "version": 2, "weekStart": "" } From dd2343224e20baaa0fce61b5ad53819c0ec21dad Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Wed, 29 May 2024 00:00:00 +0000 Subject: [PATCH 04/12] =?UTF-8?q?dex:=20=E2=AD=95=20set=20buckets=20for=20?= =?UTF-8?q?a=20specific=20list=20of=20metrics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit we can't add counters or gauges named `penumbra_dex_` because of the use of `Matcher::Prefix`. rather, we can tweak this and provide the explicit list of metrics we want buckets for. (cherry picked from commit 2b6621b20084f6eb8f555294aa6f1ab9bdce7b1c) --- crates/core/component/dex/src/component/metrics.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/core/component/dex/src/component/metrics.rs b/crates/core/component/dex/src/component/metrics.rs index 7e26a2cfa9..7dc87d2c81 100644 --- a/crates/core/component/dex/src/component/metrics.rs +++ b/crates/core/component/dex/src/component/metrics.rs @@ -85,9 +85,14 @@ where impl PrometheusBuilderExt for metrics_exporter_prometheus::PrometheusBuilder { fn set_buckets_for_dex_metrics(self) -> Result { - self.set_buckets_for_metric( - metrics_exporter_prometheus::Matcher::Prefix("penumbra_dex_".to_string()), - DEX_BUCKETS, - ) + use metrics_exporter_prometheus::Matcher::Full; + self.set_buckets_for_metric(Full(DEX_PATH_SEARCH_DURATION.to_owned()), DEX_BUCKETS)? + .set_buckets_for_metric(Full(DEX_ROUTE_FILL_DURATION.to_owned()), DEX_BUCKETS)? + .set_buckets_for_metric(Full(DEX_ARB_DURATION.to_owned()), DEX_BUCKETS)? + .set_buckets_for_metric(Full(DEX_BATCH_DURATION.to_owned()), DEX_BUCKETS)? + .set_buckets_for_metric( + Full(DEX_RPC_SIMULATE_TRADE_DURATION.to_owned()), + DEX_BUCKETS, + ) } } From 11fb0113545b94f822cb3ba975ed19f8f041e188 Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Wed, 29 May 2024 00:00:00 +0000 Subject: [PATCH 05/12] =?UTF-8?q?dex:=20=F0=9F=9A=B3=20`DEX=5FBUCKETS`=20-?= =?UTF-8?q?>=20`GENERIC=5FDEX=5FBUCKETS`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit f766aba3d6e609d034dffca86862d333b1f0071f) --- .../component/dex/src/component/metrics.rs | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/crates/core/component/dex/src/component/metrics.rs b/crates/core/component/dex/src/component/metrics.rs index 7dc87d2c81..7ebd20b290 100644 --- a/crates/core/component/dex/src/component/metrics.rs +++ b/crates/core/component/dex/src/component/metrics.rs @@ -46,7 +46,7 @@ pub fn register_metrics() { // Prometheus metrics are structured as a Histogram, rather than as a Summary. // These values may need to be updated over time. // These values are logarithmically spaced from 5ms to 250ms. -pub const DEX_BUCKETS: &[f64; 16] = &[ +const GENERIC_DEX_BUCKETS: &[f64; 16] = &[ 0.005, 0.00648985018, 0.00842363108, @@ -86,13 +86,19 @@ where impl PrometheusBuilderExt for metrics_exporter_prometheus::PrometheusBuilder { fn set_buckets_for_dex_metrics(self) -> Result { use metrics_exporter_prometheus::Matcher::Full; - self.set_buckets_for_metric(Full(DEX_PATH_SEARCH_DURATION.to_owned()), DEX_BUCKETS)? - .set_buckets_for_metric(Full(DEX_ROUTE_FILL_DURATION.to_owned()), DEX_BUCKETS)? - .set_buckets_for_metric(Full(DEX_ARB_DURATION.to_owned()), DEX_BUCKETS)? - .set_buckets_for_metric(Full(DEX_BATCH_DURATION.to_owned()), DEX_BUCKETS)? - .set_buckets_for_metric( - Full(DEX_RPC_SIMULATE_TRADE_DURATION.to_owned()), - DEX_BUCKETS, - ) + self.set_buckets_for_metric( + Full(DEX_PATH_SEARCH_DURATION.to_owned()), + GENERIC_DEX_BUCKETS, + )? + .set_buckets_for_metric( + Full(DEX_ROUTE_FILL_DURATION.to_owned()), + GENERIC_DEX_BUCKETS, + )? + .set_buckets_for_metric(Full(DEX_ARB_DURATION.to_owned()), GENERIC_DEX_BUCKETS)? + .set_buckets_for_metric(Full(DEX_BATCH_DURATION.to_owned()), GENERIC_DEX_BUCKETS)? + .set_buckets_for_metric( + Full(DEX_RPC_SIMULATE_TRADE_DURATION.to_owned()), + GENERIC_DEX_BUCKETS, + ) } } From 80194a0128ad54c023f2050a96f4d6d38772f431 Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Wed, 29 May 2024 00:00:00 +0000 Subject: [PATCH 06/12] =?UTF-8?q?dex:=20=F0=9F=9A=A0=20add=20a=20metric=20?= =?UTF-8?q?tracking=20path=20relaxing=20duration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 5c20efe254f09f395a8473101f5bbf27b6fc93eb) --- crates/core/component/dex/src/component/metrics.rs | 11 +++++++++++ .../dex/src/component/router/path_search.rs | 12 +++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/core/component/dex/src/component/metrics.rs b/crates/core/component/dex/src/component/metrics.rs index 7ebd20b290..2917d0368b 100644 --- a/crates/core/component/dex/src/component/metrics.rs +++ b/crates/core/component/dex/src/component/metrics.rs @@ -30,6 +30,11 @@ pub fn register_metrics() { Unit::Seconds, "The time spent searching for paths while executing trades within the DEX" ); + describe_counter!( + DEX_PATH_SEARCH_RELAX_PATH_DURATION, + Unit::Seconds, + "The time spent relaxing a path while routing trades within the DEX" + ); describe_histogram!( DEX_ROUTE_FILL_DURATION, Unit::Seconds, @@ -66,6 +71,8 @@ const GENERIC_DEX_BUCKETS: &[f64; 16] = &[ ]; pub const DEX_PATH_SEARCH_DURATION: &str = "penumbra_dex_path_search_duration_seconds"; +pub const DEX_PATH_SEARCH_RELAX_PATH_DURATION: &str = + "penumbra_dex_path_search_relax_path_duration_seconds"; pub const DEX_ROUTE_FILL_DURATION: &str = "penumbra_dex_route_fill_duration_seconds"; pub const DEX_ARB_DURATION: &str = "penumbra_dex_arb_duration_seconds"; pub const DEX_BATCH_DURATION: &str = "penumbra_dex_batch_duration_seconds"; @@ -90,6 +97,10 @@ impl PrometheusBuilderExt for metrics_exporter_prometheus::PrometheusBuilder { Full(DEX_PATH_SEARCH_DURATION.to_owned()), GENERIC_DEX_BUCKETS, )? + .set_buckets_for_metric( + Full(DEX_PATH_SEARCH_RELAX_PATH_DURATION.to_owned()), + GENERIC_DEX_BUCKETS, + )? .set_buckets_for_metric( Full(DEX_ROUTE_FILL_DURATION.to_owned()), GENERIC_DEX_BUCKETS, diff --git a/crates/core/component/dex/src/component/router/path_search.rs b/crates/core/component/dex/src/component/router/path_search.rs index 92e114b198..3fffa26261 100644 --- a/crates/core/component/dex/src/component/router/path_search.rs +++ b/crates/core/component/dex/src/component/router/path_search.rs @@ -6,6 +6,7 @@ use cnidarium::{StateDelta, StateRead}; use futures::StreamExt; use penumbra_asset::asset; use penumbra_num::fixpoint::U128x128; +use tap::Tap; use tokio::task::JoinSet; use tracing::{instrument, Instrument}; @@ -88,7 +89,16 @@ async fn relax_active_paths( "relaxing active paths" ); for path in active_paths { - js.spawn(relax_path(cache.clone(), path, fixed_candidates.clone())); + let candidates = Arc::clone(&fixed_candidates); + let cache = Arc::clone(&cache); + js.spawn(async move { + use crate::component::metrics::DEX_PATH_SEARCH_RELAX_PATH_DURATION; + let metric = metrics::histogram!(DEX_PATH_SEARCH_RELAX_PATH_DURATION); + let start = std::time::Instant::now(); + relax_path(cache, path, candidates) + .await + .tap(|_| metric.record(start.elapsed())) + }); } // Wait for all relaxations to complete. while let Some(task) = js.join_next().await { From 70672b10672f8fe98f42918f8e7a9d6173134d40 Mon Sep 17 00:00:00 2001 From: Erwan Or Date: Wed, 12 Jun 2024 17:24:59 -0400 Subject: [PATCH 07/12] dex(metrics): set buckets around expected latency region (#4600) ## Describe your changes This re-centers the DEX buckets around the expected latency region ## Checklist before requesting a review - [x] If this code contains consensus-breaking changes, I have added the "consensus-breaking" label. Otherwise, I declare my belief that there are not consensus-breaking changes, for the following reason: (cherry picked from commit 06a08f7aa31d2172fd892f44cd358b94881f23a6) --- crates/core/component/dex/src/component/metrics.rs | 12 ++++++------ deployments/compose/metrics.yml | 3 +-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/core/component/dex/src/component/metrics.rs b/crates/core/component/dex/src/component/metrics.rs index 2917d0368b..c894307077 100644 --- a/crates/core/component/dex/src/component/metrics.rs +++ b/crates/core/component/dex/src/component/metrics.rs @@ -50,8 +50,13 @@ pub fn register_metrics() { // We configure buckets for the DEX routing times manually, in order to ensure // Prometheus metrics are structured as a Histogram, rather than as a Summary. // These values may need to be updated over time. -// These values are logarithmically spaced from 5ms to 250ms. +// These values are logarithmically spaced from 5us to 67ms. const GENERIC_DEX_BUCKETS: &[f64; 16] = &[ + 0.0005, + 0.000792, + 0.001256, + 0.001991, + 0.003155, 0.005, 0.00648985018, 0.00842363108, @@ -63,11 +68,6 @@ const GENERIC_DEX_BUCKETS: &[f64; 16] = &[ 0.0402798032, 0.05228197763, 0.06786044041, - 0.08808081833, - 0.11432626298, - 0.14839206374, - 0.1926084524, - 0.250, ]; pub const DEX_PATH_SEARCH_DURATION: &str = "penumbra_dex_path_search_duration_seconds"; diff --git a/deployments/compose/metrics.yml b/deployments/compose/metrics.yml index 5121cb15a7..32343a156c 100644 --- a/deployments/compose/metrics.yml +++ b/deployments/compose/metrics.yml @@ -19,5 +19,4 @@ services: image: "docker.io/prom/prometheus" network_mode: host volumes: - # TODO: this path is not accurate - - /home/conor/src/penumbra/deployments/config/prometheus.yml:/etc/prometheus/prometheus.yml:ro + - "${PWD:?}/../config/prometheus.yml:/etc/prometheus/prometheus.yml:ro" From 01436701b9d90494cd09fd6138a8a13ec7e55ff8 Mon Sep 17 00:00:00 2001 From: the letter L <134443988+turbocrime@users.noreply.github.com> Date: Fri, 14 Jun 2024 11:06:14 -0700 Subject: [PATCH 08/12] sct(component): add `TimestampByHeight` to query service (#4523) Adds a TimestampByHeightRequest to SCT QueryService. There is no corresponding migration added, so any block heights lower than the height at which this is deployed will have missing data. https://github.com/penumbra-zone/penumbra/issues/4522 - [ ] If this code contains consensus-breaking changes, I have added the "consensus-breaking" label. Otherwise, I declare my belief that there are not consensus-breaking changes, for the following reason: > This adds a pb API endpoint and should not affect consensus. New data is tracked in nonconsensus storage only. --------- Co-authored-by: turbocrime Co-authored-by: Chris Czub (cherry picked from commit 989d3a923d34df274b732ffdfef5be9344db3895) --- Cargo.lock | 2 + crates/bin/pd/src/migrate.rs | 2 +- .../src/gen/proto_descriptor.bin.no_lfs | Bin 99489 -> 96180 bytes crates/core/app/src/penumbra_host_chain.rs | 2 +- .../component/ibc/src/component/client.rs | 6 +- .../component/ibc/src/component/packet.rs | 2 +- crates/core/component/sct/Cargo.toml | 2 + .../core/component/sct/src/component/clock.rs | 44 +++- .../core/component/sct/src/component/rpc.rs | 24 +++ .../core/component/sct/src/component/sct.rs | 2 +- crates/core/component/sct/src/state_key.rs | 6 +- .../src/gen/penumbra.core.component.sct.v1.rs | 110 ++++++++++ .../penumbra.core.component.sct.v1.serde.rs | 193 ++++++++++++++++++ .../proto/src/gen/proto_descriptor.bin.no_lfs | Bin 534187 -> 531429 bytes .../penumbra/core/component/sct/v1/sct.proto | 10 + 15 files changed, 389 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 70a5da8ba6..ec86a885c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5524,6 +5524,7 @@ dependencies = [ "bincode", "blake2b_simd 1.0.2", "bytes", + "chrono", "cnidarium", "cnidarium-component", "decaf377 0.5.0", @@ -5532,6 +5533,7 @@ dependencies = [ "im", "metrics", "once_cell", + "pbjson-types", "penumbra-keys", "penumbra-proto", "penumbra-tct", diff --git a/crates/bin/pd/src/migrate.rs b/crates/bin/pd/src/migrate.rs index 862a395e42..de57b970e9 100644 --- a/crates/bin/pd/src/migrate.rs +++ b/crates/bin/pd/src/migrate.rs @@ -147,7 +147,7 @@ pub async fn last_block_timestamp(home: PathBuf) -> anyhow::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 IL?)Exc)#m};G%A> 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 Pj+;1X{o|h1=HdSZt^gQ= delta 23133 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 PW?Ie6rpLbO@QeQfKu^ur diff --git a/crates/core/app/src/penumbra_host_chain.rs b/crates/core/app/src/penumbra_host_chain.rs index 0fc8cf53a8..f394c06edf 100644 --- a/crates/core/app/src/penumbra_host_chain.rs +++ b/crates/core/app/src/penumbra_host_chain.rs @@ -26,6 +26,6 @@ impl HostInterface for PenumbraHost { async fn get_block_timestamp( state: S, ) -> anyhow::Result { - state.get_block_timestamp().await + state.get_current_block_timestamp().await } } diff --git a/crates/core/component/ibc/src/component/client.rs b/crates/core/component/ibc/src/component/client.rs index d65347dc57..33240c591b 100644 --- a/crates/core/component/ibc/src/component/client.rs +++ b/crates/core/component/ibc/src/component/client.rs @@ -293,7 +293,7 @@ pub trait StateReadExt: StateRead + penumbra_sct::component::clock::EpochRead { let latest_consensus_state = latest_consensus_state.expect("latest consensus state is Ok"); - let current_block_time = self.get_block_timestamp().await; + let current_block_time = self.get_current_block_timestamp().await; if current_block_time.is_err() { return ClientStatus::Unknown; @@ -490,7 +490,7 @@ mod tests { } async fn get_block_timestamp(state: S) -> Result { - state.get_block_timestamp().await + state.get_current_block_timestamp().await } } @@ -609,7 +609,7 @@ mod tests { // available to the unit test. let timestamp = Time::parse_from_rfc3339("2022-02-11T17:30:50.425417198Z")?; let mut state_tx = state.try_begin_transaction().unwrap(); - state_tx.put_block_timestamp(timestamp); + state_tx.put_block_timestamp(1u64, timestamp); state_tx.put_block_height(1); state_tx.put_ibc_params(crate::params::IBCParameters { ibc_enabled: true, diff --git a/crates/core/component/ibc/src/component/packet.rs b/crates/core/component/ibc/src/component/packet.rs index da3c496c79..71ad23874e 100644 --- a/crates/core/component/ibc/src/component/packet.rs +++ b/crates/core/component/ibc/src/component/packet.rs @@ -134,7 +134,7 @@ pub trait SendPacketRead: StateRead { .get_verified_consensus_state(&client_state.latest_height(), &connection.client_id) .await?; - let current_block_time = self.get_block_timestamp().await?; + let current_block_time = self.get_current_block_timestamp().await?; let time_elapsed = current_block_time.duration_since(latest_consensus_state.timestamp)?; if client_state.expired(time_elapsed) { diff --git a/crates/core/component/sct/Cargo.toml b/crates/core/component/sct/Cargo.toml index 687c387fb6..a29dd5a705 100644 --- a/crates/core/component/sct/Cargo.toml +++ b/crates/core/component/sct/Cargo.toml @@ -33,6 +33,7 @@ hex = {workspace = true} im = {workspace = true} metrics = {workspace = true} once_cell = {workspace = true} +pbjson-types = {workspace = true} penumbra-keys = {workspace = true, default-features = false} penumbra-proto = {workspace = true, default-features = false} penumbra-tct = {workspace = true, default-features = true} @@ -43,3 +44,4 @@ serde = {workspace = true, features = ["derive"]} tendermint = {workspace = true} tonic = {workspace = true, optional = true} tracing = {workspace = true} +chrono = { workspace = true, default-features = false, features = ["serde"] } diff --git a/crates/core/component/sct/src/component/clock.rs b/crates/core/component/sct/src/component/clock.rs index 82ecea9941..e74b1aec84 100644 --- a/crates/core/component/sct/src/component/clock.rs +++ b/crates/core/component/sct/src/component/clock.rs @@ -25,14 +25,35 @@ pub trait EpochRead: StateRead { /// /// # Panic /// Panics if the block timestamp is not a valid RFC3339 time string. - async fn get_block_timestamp(&self) -> Result { + async fn get_current_block_timestamp(&self) -> Result { let timestamp_string: String = self - .get_proto(state_key::block_manager::block_timestamp()) + .get_proto(state_key::block_manager::current_block_timestamp()) .await? - .ok_or_else(|| anyhow!("Missing block_timestamp"))?; + .ok_or_else(|| anyhow!("Missing current_block_timestamp"))?; Ok(tendermint::Time::from_str(×tamp_string) - .context("block_timestamp was an invalid RFC3339 time string")?) + .context("current_block_timestamp was an invalid RFC3339 time string")?) + } + + /// Gets a historic block timestamp from nonverifiable storage. + /// + /// # Errors + /// Returns an error if the block timestamp is missing. + /// + /// # Panic + /// Panics if the block timestamp is not a valid RFC3339 time string. + async fn get_block_timestamp(&self, height: u64) -> Result { + let timestamp_string: String = self + .nonverifiable_get_proto(&state_key::block_manager::block_timestamp(height).as_bytes()) + .await? + .ok_or_else(|| anyhow!("Missing block_timestamp for height {}", height))?; + + Ok( + tendermint::Time::from_str(×tamp_string).context(format!( + "block_timestamp for height {} was an invalid RFC3339 time string", + height + ))?, + ) } /// Get the current application epoch. @@ -72,12 +93,19 @@ impl EpochRead for T {} /// as well as related data like reported timestamps and epoch duration. #[async_trait] pub trait EpochManager: StateWrite { - /// Writes the block timestamp as an RFC3339 string to verifiable storage. - fn put_block_timestamp(&mut self, timestamp: tendermint::Time) { + /// Writes the current block's timestamp as an RFC3339 string to verifiable storage. + /// + /// Also writes the current block's timestamp to the appropriate key in nonverifiable storage. + fn put_block_timestamp(&mut self, height: u64, timestamp: tendermint::Time) { self.put_proto( - state_key::block_manager::block_timestamp().into(), + state_key::block_manager::current_block_timestamp().into(), timestamp.to_rfc3339(), - ) + ); + + self.nonverifiable_put_proto( + state_key::block_manager::block_timestamp(height).into(), + timestamp.to_rfc3339(), + ); } /// Write a value in the end epoch flag in object-storage. diff --git a/crates/core/component/sct/src/component/rpc.rs b/crates/core/component/sct/src/component/rpc.rs index 2cb6b69c6e..7a33abce3e 100644 --- a/crates/core/component/sct/src/component/rpc.rs +++ b/crates/core/component/sct/src/component/rpc.rs @@ -1,7 +1,9 @@ use cnidarium::Storage; +use pbjson_types::Timestamp; use penumbra_proto::core::component::sct::v1::query_service_server::QueryService; use penumbra_proto::core::component::sct::v1::{ AnchorByHeightRequest, AnchorByHeightResponse, EpochByHeightRequest, EpochByHeightResponse, + TimestampByHeightRequest, TimestampByHeightResponse, }; use tonic::Status; use tracing::instrument; @@ -55,4 +57,26 @@ impl QueryService for Server { anchor: anchor.map(Into::into), })) } + + #[instrument(skip(self, request))] + async fn timestamp_by_height( + &self, + request: tonic::Request, + ) -> Result, Status> { + let state = self.storage.latest_snapshot(); + + let height = request.get_ref().height; + let block_time = state.get_block_timestamp(height).await.map_err(|e| { + tonic::Status::unknown(format!("could not get timestamp for height {height}: {e}")) + })?; + let timestamp = chrono::DateTime::parse_from_rfc3339(block_time.to_rfc3339().as_str()) + .expect("timestamp should roundtrip to string"); + + Ok(tonic::Response::new(TimestampByHeightResponse { + timestamp: Some(Timestamp { + seconds: timestamp.timestamp(), + nanos: timestamp.timestamp_subsec_nanos() as i32, + }), + })) + } } diff --git a/crates/core/component/sct/src/component/sct.rs b/crates/core/component/sct/src/component/sct.rs index 74e3ae6c97..a209ea6ad1 100644 --- a/crates/core/component/sct/src/component/sct.rs +++ b/crates/core/component/sct/src/component/sct.rs @@ -53,7 +53,7 @@ impl Component for Sct { ) { let state = Arc::get_mut(state).expect("there's only one reference to the state"); state.put_block_height(begin_block.header.height.into()); - state.put_block_timestamp(begin_block.header.time); + state.put_block_timestamp(begin_block.header.height.into(), begin_block.header.time); } #[instrument(name = "sct_component", skip(_state, _end_block))] diff --git a/crates/core/component/sct/src/state_key.rs b/crates/core/component/sct/src/state_key.rs index 1f28ee2a64..93a32120cb 100644 --- a/crates/core/component/sct/src/state_key.rs +++ b/crates/core/component/sct/src/state_key.rs @@ -9,9 +9,13 @@ pub mod block_manager { "sct/block_manager/block_height" } - pub fn block_timestamp() -> &'static str { + pub fn current_block_timestamp() -> &'static str { "sct/block_manager/block_timestamp" } + + pub fn block_timestamp(height: u64) -> String { + format!("sct/block_manager/historical_block_timestamp/{}", height) + } } pub mod epoch_manager { diff --git a/crates/proto/src/gen/penumbra.core.component.sct.v1.rs b/crates/proto/src/gen/penumbra.core.component.sct.v1.rs index 9e2ba394c9..6f132c5141 100644 --- a/crates/proto/src/gen/penumbra.core.component.sct.v1.rs +++ b/crates/proto/src/gen/penumbra.core.component.sct.v1.rs @@ -327,6 +327,32 @@ impl ::prost::Name for AnchorByHeightResponse { ::prost::alloc::format!("penumbra.core.component.sct.v1.{}", Self::NAME) } } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TimestampByHeightRequest { + #[prost(uint64, tag = "1")] + pub height: u64, +} +impl ::prost::Name for TimestampByHeightRequest { + const NAME: &'static str = "TimestampByHeightRequest"; + const PACKAGE: &'static str = "penumbra.core.component.sct.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("penumbra.core.component.sct.v1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TimestampByHeightResponse { + #[prost(message, optional, tag = "1")] + pub timestamp: ::core::option::Option<::pbjson_types::Timestamp>, +} +impl ::prost::Name for TimestampByHeightResponse { + const NAME: &'static str = "TimestampByHeightResponse"; + const PACKAGE: &'static str = "penumbra.core.component.sct.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("penumbra.core.component.sct.v1.{}", Self::NAME) + } +} /// Generated client implementations. #[cfg(feature = "rpc")] pub mod query_service_client { @@ -474,6 +500,36 @@ pub mod query_service_client { ); self.inner.unary(req, path, codec).await } + pub async fn timestamp_by_height( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/penumbra.core.component.sct.v1.QueryService/TimestampByHeight", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "penumbra.core.component.sct.v1.QueryService", + "TimestampByHeight", + ), + ); + self.inner.unary(req, path, codec).await + } } } /// Generated server implementations. @@ -498,6 +554,13 @@ pub mod query_service_server { tonic::Response, tonic::Status, >; + async fn timestamp_by_height( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; } /// Query operations for the SCT component. #[derive(Debug)] @@ -671,6 +734,53 @@ pub mod query_service_server { }; Box::pin(fut) } + "/penumbra.core.component.sct.v1.QueryService/TimestampByHeight" => { + #[allow(non_camel_case_types)] + struct TimestampByHeightSvc(pub Arc); + impl< + T: QueryService, + > tonic::server::UnaryService + for TimestampByHeightSvc { + type Response = super::TimestampByHeightResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::timestamp_by_height(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = TimestampByHeightSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } _ => { Box::pin(async move { Ok( diff --git a/crates/proto/src/gen/penumbra.core.component.sct.v1.serde.rs b/crates/proto/src/gen/penumbra.core.component.sct.v1.serde.rs index e8536e4abe..6618d12701 100644 --- a/crates/proto/src/gen/penumbra.core.component.sct.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.core.component.sct.v1.serde.rs @@ -2022,3 +2022,196 @@ impl<'de> serde::Deserialize<'de> for SctParameters { deserializer.deserialize_struct("penumbra.core.component.sct.v1.SctParameters", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for TimestampByHeightRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.height != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.component.sct.v1.TimestampByHeightRequest", len)?; + if self.height != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("height", ToString::to_string(&self.height).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for TimestampByHeightRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "height", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Height, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "height" => Ok(GeneratedField::Height), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = TimestampByHeightRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.component.sct.v1.TimestampByHeightRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut height__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Height => { + if height__.is_some() { + return Err(serde::de::Error::duplicate_field("height")); + } + height__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(TimestampByHeightRequest { + height: height__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("penumbra.core.component.sct.v1.TimestampByHeightRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for TimestampByHeightResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.timestamp.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.component.sct.v1.TimestampByHeightResponse", len)?; + if let Some(v) = self.timestamp.as_ref() { + struct_ser.serialize_field("timestamp", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for TimestampByHeightResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "timestamp", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Timestamp, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "timestamp" => Ok(GeneratedField::Timestamp), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = TimestampByHeightResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.component.sct.v1.TimestampByHeightResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut timestamp__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Timestamp => { + if timestamp__.is_some() { + return Err(serde::de::Error::duplicate_field("timestamp")); + } + timestamp__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(TimestampByHeightResponse { + timestamp: timestamp__, + }) + } + } + deserializer.deserialize_struct("penumbra.core.component.sct.v1.TimestampByHeightResponse", FIELDS, GeneratedVisitor) + } +} diff --git a/crates/proto/src/gen/proto_descriptor.bin.no_lfs b/crates/proto/src/gen/proto_descriptor.bin.no_lfs index c2fd4bd1f134c9aff4f19c3160df9b9c093cfa26..0c71d1eebcdafe595afa0dc935b3b6e66067646c 100644 GIT binary patch delta 22810 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 Date: Wed, 19 Jun 2024 13:01:59 -0400 Subject: [PATCH 09/12] Split CloseAll/WithdrawAll iteration into chunks to prevent planner failures (#4642) ## Describe your changes The `pcli tx position close-all` and `pcli tx position withdraw-all` commands were failing to plan for me when I had a significant (100+) number of positions. This PR splits them into chunks, currently 30 positions, and fixed the planning failures for me. ## Checklist before requesting a review - [x] If this code contains consensus-breaking changes, I have added the "consensus-breaking" label. Otherwise, I declare my belief that there are not consensus-breaking changes, for the following reason: > pcli only (cherry picked from commit 1693c20768c09836ad54cb8ab4df1c55d54f41ba) --- crates/bin/pcli/src/command/tx.rs | 141 ++++++++++++++++++------------ 1 file changed, 84 insertions(+), 57 deletions(-) diff --git a/crates/bin/pcli/src/command/tx.rs b/crates/bin/pcli/src/command/tx.rs index 7657114771..5eaa0ba983 100644 --- a/crates/bin/pcli/src/command/tx.rs +++ b/crates/bin/pcli/src/command/tx.rs @@ -69,6 +69,11 @@ mod liquidity_position; mod proposal; mod replicate; +/// The planner can fail to build a large transaction, so +/// pcli splits apart the number of positions to close/withdraw +/// in the [`PositionCmd::CloseAll`]/[`PositionCmd::WithdrawAll`] commands. +const POSITION_CHUNK_SIZE: usize = 30; + #[derive(Debug, clap::Subcommand)] pub enum TxCmd { /// Auction related commands. @@ -1112,25 +1117,36 @@ impl TxCmd { return Ok(()); } + println!( + "{} total open positions, closing in {} batches of {}", + owned_position_ids.len(), + owned_position_ids.len() / POSITION_CHUNK_SIZE + 1, + POSITION_CHUNK_SIZE + ); + let mut planner = Planner::new(OsRng); - planner - .set_gas_prices(gas_prices) - .set_fee_tier((*fee_tier).into()); - for position_id in owned_position_ids { - // Close the position - planner.position_close(position_id); - } + // Close 5 positions in a single transaction to avoid planner failures. + for positions_to_close_now in owned_position_ids.chunks(POSITION_CHUNK_SIZE) { + planner + .set_gas_prices(gas_prices) + .set_fee_tier((*fee_tier).into()); - let final_plan = planner - .plan( - app.view - .as_mut() - .context("view service must be initialized")?, - AddressIndex::new(*source), - ) - .await?; - app.build_and_submit_transaction(final_plan).await?; + for position_id in positions_to_close_now { + // Close the position + planner.position_close(*position_id); + } + + let final_plan = planner + .plan( + app.view + .as_mut() + .context("view service must be initialized")?, + AddressIndex::new(*source), + ) + .await?; + app.build_and_submit_transaction(final_plan).await?; + } } TxCmd::Position(PositionCmd::WithdrawAll { source, @@ -1151,53 +1167,64 @@ impl TxCmd { return Ok(()); } - let mut planner = Planner::new(OsRng); - planner - .set_gas_prices(gas_prices) - .set_fee_tier((*fee_tier).into()); + println!( + "{} total closed positions, withdrawing in {} batches of {}", + owned_position_ids.len(), + owned_position_ids.len() / POSITION_CHUNK_SIZE + 1, + POSITION_CHUNK_SIZE, + ); let mut client = DexQueryServiceClient::new(app.pd_channel().await?); - for position_id in owned_position_ids { - // Withdraw the position + let mut planner = Planner::new(OsRng); - // Fetch the information regarding the position from the view service. - let position = client - .liquidity_position_by_id(LiquidityPositionByIdRequest { - position_id: Some(position_id.into()), - }) - .await? - .into_inner(); + // Withdraw 5 positions in a single transaction to avoid planner failures. + for positions_to_withdraw_now in owned_position_ids.chunks(POSITION_CHUNK_SIZE) { + planner + .set_gas_prices(gas_prices) + .set_fee_tier((*fee_tier).into()); - let reserves = position - .data - .clone() - .expect("missing position metadata") - .reserves - .expect("missing position reserves"); - let pair = position - .data - .expect("missing position") - .phi - .expect("missing position trading function") - .pair - .expect("missing trading function pair"); - planner.position_withdraw( - position_id, - reserves.try_into().expect("invalid reserves"), - pair.try_into().expect("invalid pair"), - ); - } + for position_id in positions_to_withdraw_now { + // Withdraw the position - let final_plan = planner - .plan( - app.view - .as_mut() - .context("view service must be initialized")?, - AddressIndex::new(*source), - ) - .await?; - app.build_and_submit_transaction(final_plan).await?; + // Fetch the information regarding the position from the view service. + let position = client + .liquidity_position_by_id(LiquidityPositionByIdRequest { + position_id: Some((*position_id).into()), + }) + .await? + .into_inner(); + + let reserves = position + .data + .clone() + .expect("missing position metadata") + .reserves + .expect("missing position reserves"); + let pair = position + .data + .expect("missing position") + .phi + .expect("missing position trading function") + .pair + .expect("missing trading function pair"); + planner.position_withdraw( + *position_id, + reserves.try_into().expect("invalid reserves"), + pair.try_into().expect("invalid pair"), + ); + } + + let final_plan = planner + .plan( + app.view + .as_mut() + .context("view service must be initialized")?, + AddressIndex::new(*source), + ) + .await?; + app.build_and_submit_transaction(final_plan).await?; + } } TxCmd::Position(PositionCmd::Withdraw { source, From b061a0d258639a77ba0686c0cc83be7b038a44dd Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Tue, 11 Jun 2024 00:00:00 +0000 Subject: [PATCH 10/12] =?UTF-8?q?pcli:=20=F0=9F=8C=B7=20add=20a=20top-leve?= =?UTF-8?q?l=20`--grpc-url`=20override?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit when a pcli user initializes their configuration, they provide the `init` command with the grpc url of a fullnode, which is stored in pcli's configuration file. by default, this is found at `~/.local/share/pcli/config.toml`. if that fullnode is later encountering issues, this can render many pcli commands unusable, without a clear workaround. this is a small patch, providing a top-level `--grpc-url` option that will override the config file's GRPC url. this can help users temporarily send requests to a different fullnode, until their preferred default comes back online. (cherry picked from commit 3f7ccbd7bd91bc67c05a4d46d515ca5abdf3f241) --- crates/bin/pcli/src/opt.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/bin/pcli/src/opt.rs b/crates/bin/pcli/src/opt.rs index c1ed746db0..c2d3e608b5 100644 --- a/crates/bin/pcli/src/opt.rs +++ b/crates/bin/pcli/src/opt.rs @@ -18,6 +18,7 @@ use penumbra_proto::{ use penumbra_view::ViewServer; use std::io::IsTerminal as _; use tracing_subscriber::EnvFilter; +use url::Url; #[derive(Debug, Parser)] #[clap(name = "pcli", about = "The Penumbra command-line interface.", version)] @@ -27,6 +28,11 @@ pub struct Opt { /// The home directory used to store configuration and data. #[clap(long, default_value_t = default_home(), env = "PENUMBRA_PCLI_HOME")] pub home: Utf8PathBuf, + /// Override the GRPC URL that will be used to connect to a fullnode. + /// + /// By default, this URL is provided by pcli's config. See `pcli init` for more information. + #[clap(long, parse(try_from_str = Url::parse))] + pub grpc_url: Option, } impl Opt { @@ -49,7 +55,11 @@ impl Opt { pub fn load_config(&self) -> Result { let path = self.home.join(crate::CONFIG_FILE_NAME); - PcliConfig::load(path) + let mut config = PcliConfig::load(path)?; + if let Some(grpc_url) = &self.grpc_url { + config.grpc_url = grpc_url.clone(); + } + Ok(config) } pub async fn into_app(self) -> Result<(App, Command)> { From 5ecf8906cc0f9942e736c68d7c58db382fbd59a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=BAc=C3=A1s=20Meier?= Date: Mon, 10 Jun 2024 13:32:52 -0700 Subject: [PATCH 11/12] View Service: Don't sync the same block twice (#4578) This should fix an issue wherein a load balanced RPC can cause data corruption by delivering the same block twice in the stream. ## Issue ticket number and link This should close #4577. ## Checklist before requesting a review - [x] If this code contains consensus-breaking changes, I have added the "consensus-breaking" label. Otherwise, I declare my belief that there are not consensus-breaking changes, for the following reason: > Just a client change (cherry picked from commit 4245ebcb8def4930ea3875af4f119489efe4cce2) --- crates/view/src/worker.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/view/src/worker.rs b/crates/view/src/worker.rs index 996a6405c9..56bab3e862 100644 --- a/crates/view/src/worker.rs +++ b/crates/view/src/worker.rs @@ -231,10 +231,17 @@ impl Worker { } }); + let mut expected_height = start_height; + while let Some(block) = buffered_stream.recv().await { let block: CompactBlock = block?.try_into()?; let height = block.height; + if height != expected_height { + tracing::warn!("out of order block detected"); + continue; + } + expected_height += 1; // Lock the SCT only while processing this block. let mut sct_guard = self.sct.write().await; From 9d0f904a59159e20917995e56e71b66cb30f208c Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 11 Jun 2024 13:19:50 -0700 Subject: [PATCH 12/12] compact-block: try to ensure response consistency (cherry picked from commit 516b1a1e8b61d66beb72ce6b3e899f9320864c30) --- .../compact-block/src/component/rpc.rs | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/crates/core/component/compact-block/src/component/rpc.rs b/crates/core/component/compact-block/src/component/rpc.rs index 13b373bffa..b277e3a6bf 100644 --- a/crates/core/component/compact-block/src/component/rpc.rs +++ b/crates/core/component/compact-block/src/component/rpc.rs @@ -4,8 +4,8 @@ use anyhow::bail; use cnidarium::Storage; use futures::{StreamExt, TryFutureExt, TryStreamExt}; use penumbra_proto::core::component::compact_block::v1::{ - query_service_server::QueryService, CompactBlockRangeRequest, CompactBlockRangeResponse, - CompactBlockRequest, CompactBlockResponse, + query_service_server::QueryService, CompactBlock, CompactBlockRangeRequest, + CompactBlockRangeResponse, CompactBlockRequest, CompactBlockResponse, }; use penumbra_sct::component::clock::EpochRead; use tokio::sync::mpsc; @@ -103,6 +103,11 @@ impl QueryService for Server { let (tx_blocks, rx_blocks) = mpsc::channel(10); let tx_blocks_err = tx_blocks.clone(); + // Wrap the block sender in a guard that ensures we only send the expected next block + let mut tx_blocks = BlockSender { + next_height: start_height, + inner: tx_blocks, + }; tokio::spawn( async move { let _guard = CompactBlockConnectionCounter::new(); @@ -142,7 +147,7 @@ impl QueryService for Server { // Future iterations of this work should start by moving block serialization // outside of the `send_op` future, and investigate if long blocking sends can // happen for benign reasons (i.e not caused by the client). - tx_blocks.send(Ok(compact_block)).await?; + tx_blocks.send(compact_block).await?; metrics::counter!(metrics::COMPACT_BLOCK_RANGE_SERVED_TOTAL).increment(1); } @@ -171,10 +176,7 @@ impl QueryService for Server { .await .expect("no error fetching block") .expect("compact block for in-range height must be present"); - tx_blocks - .send(Ok(block)) - .await - .map_err(|_| tonic::Status::cancelled("client closed connection"))?; + tx_blocks.send(block).await?; metrics::counter!(metrics::COMPACT_BLOCK_RANGE_SERVED_TOTAL).increment(1); } @@ -200,10 +202,7 @@ impl QueryService for Server { .await .map_err(|e| tonic::Status::internal(e.to_string()))? .expect("compact block for in-range height must be present"); - tx_blocks - .send(Ok(block)) - .await - .map_err(|_| tonic::Status::cancelled("channel closed"))?; + tx_blocks.send(block).await?; metrics::counter!(metrics::COMPACT_BLOCK_RANGE_SERVED_TOTAL).increment(1); } } @@ -250,3 +249,24 @@ impl Drop for CompactBlockConnectionCounter { metrics::gauge!(metrics::COMPACT_BLOCK_RANGE_ACTIVE_CONNECTIONS).decrement(1.0); } } + +/// Stateful wrapper for a mpsc that tracks the outbound height +struct BlockSender { + next_height: u64, + inner: mpsc::Sender>, +} + +impl BlockSender { + async fn send(&mut self, block: CompactBlock) -> anyhow::Result<()> { + if block.height != self.next_height { + bail!( + "block height mismatch while sending: expected {}, got {}", + self.next_height, + block.height + ); + } + self.inner.send(Ok(block)).await?; + self.next_height += 1; + Ok(()) + } +}