From 45cfa8aad912560bb2c5aeb3ee8de75c44d689a8 Mon Sep 17 00:00:00 2001 From: Andre da Silva <2917611+ndr-ds@users.noreply.github.com> Date: Tue, 9 Sep 2025 02:13:38 -0300 Subject: [PATCH 1/5] eBPF Pyroscope (#4469) ## Motivation We're adding Pyroscope support back, but using eBPF this time ## Proposal * Add Pyroscope to our helm chart * Add a Grafana dashboard with the profiling info ## Test Plan Deployed a network and saw the Pyroscope profiling data both on the Pyroscope UI and on Grafana, as well as logs from Pyroscope on Loki: ![Screenshot 2025-09-02 at 19.31.14.png](https://app.graphite.dev/user-attachments/assets/e02b7de5-3359-462c-8eec-f4ccf7b46456.png) ![Screenshot 2025-09-02 at 19.31.04.png](https://app.graphite.dev/user-attachments/assets/cbbd7e08-34c3-4152-bd77-68525734021a.png) ![Screenshot 2025-09-02 at 19.30.55.png](https://app.graphite.dev/user-attachments/assets/00435910-5842-4c09-b4aa-634c6e51eace.png) ## Release Plan - Nothing to do / These changes follow the usual release cycle. --- kubernetes/linera-validator/Chart.lock | 7 +- kubernetes/linera-validator/Chart.yaml | 4 + .../charts/pyroscope-1.14.2.tgz | Bin 0 -> 95725 bytes .../grafana-dashboards/profiling/cpu.json | 432 ++++++++++++++++++ .../grafana-pyroscope-dashboard-config.yaml | 10 + 5 files changed, 451 insertions(+), 2 deletions(-) create mode 100644 kubernetes/linera-validator/charts/pyroscope-1.14.2.tgz create mode 100644 kubernetes/linera-validator/grafana-dashboards/profiling/cpu.json create mode 100644 kubernetes/linera-validator/templates/grafana-pyroscope-dashboard-config.yaml diff --git a/kubernetes/linera-validator/Chart.lock b/kubernetes/linera-validator/Chart.lock index c4d2e00ef72c..f0f67683ccaa 100644 --- a/kubernetes/linera-validator/Chart.lock +++ b/kubernetes/linera-validator/Chart.lock @@ -5,5 +5,8 @@ dependencies: - name: loki-stack repository: https://grafana.github.io/helm-charts version: 2.8.9 -digest: sha256:da3a8808fb9479bdd183b307e70d7855794739cafc1796047bde8febc78d539c -generated: "2023-11-30T17:19:14.544457977Z" +- name: pyroscope + repository: https://grafana.github.io/helm-charts + version: 1.14.2 +digest: sha256:7fe611b57ddb6d72aa31bac87568fdb8e531e988e2ce4067b931d3026332f027 +generated: "2025-09-01T16:56:59.19795-03:00" diff --git a/kubernetes/linera-validator/Chart.yaml b/kubernetes/linera-validator/Chart.yaml index 9ad89e557b26..301d59f669c4 100644 --- a/kubernetes/linera-validator/Chart.yaml +++ b/kubernetes/linera-validator/Chart.yaml @@ -32,3 +32,7 @@ dependencies: - name: loki-stack version: "2.8.9" repository: "https://grafana.github.io/helm-charts" + + - name: pyroscope + version: "1.14.2" + repository: "https://grafana.github.io/helm-charts" diff --git a/kubernetes/linera-validator/charts/pyroscope-1.14.2.tgz b/kubernetes/linera-validator/charts/pyroscope-1.14.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..7d2b8fa12c8e098bfb54697b44fc66ea0a913e33 GIT binary patch literal 95725 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 Date: Tue, 16 Sep 2025 16:00:44 -0300 Subject: [PATCH 2/5] Optimize RocksDB prefix searches (#4487) ## Motivation Optimize RocksDB performance for prefix scans ## Proposal Enhance the RocksDB backend with several performance optimizations: - Add optimized `ReadOptions` for prefix scans with async I/O enabled - Set precise upper bounds for iterators to minimize key traversal - Improve iterator validity checking with a more robust loop structure - Configure bloom filters for prefix iteration optimization - Increase block size from 4KB to 32KB to reduce iterator seeks - Set up prefix extraction for bloom filter optimization - Enable memory-mapped files for faster reads - Configure memtable bloom filters and other performance settings ## Test Plan Tested this with the benchmarks, saw a performance improvement. Not a step change improvement, but significant enough to warrant a PR ## Release Plan Nothing to do / These changes follow the usual release cycle. ## Links - [reviewer checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist) --- linera-views/src/backends/rocks_db.rs | 72 +++++++++++++++++++-------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/linera-views/src/backends/rocks_db.rs b/linera-views/src/backends/rocks_db.rs index ce9ca3172549..1d3dfcd18c94 100644 --- a/linera-views/src/backends/rocks_db.rs +++ b/linera-views/src/backends/rocks_db.rs @@ -14,7 +14,7 @@ use std::{ }; use linera_base::ensure; -use rocksdb::{BlockBasedOptions, Cache, DBCompactionStyle}; +use rocksdb::{BlockBasedOptions, Cache, DBCompactionStyle, SliceTransform}; use serde::{Deserialize, Serialize}; use sysinfo::{CpuRefreshKind, MemoryRefreshKind, RefreshKind, System}; use tempfile::TempDir; @@ -166,25 +166,38 @@ impl RocksDbStoreExecutor { Ok(entries.into_iter().collect::>()?) } + fn get_find_prefix_iterator(&self, prefix: &[u8]) -> rocksdb::DBRawIteratorWithThreadMode { + // Configure ReadOptions optimized for SSDs and iterator performance + let mut read_opts = rocksdb::ReadOptions::default(); + // Enable async I/O for better concurrency + read_opts.set_async_io(true); + + // Set precise upper bound to minimize key traversal + let upper_bound = get_upper_bound_option(prefix); + if let Some(upper_bound) = upper_bound { + read_opts.set_iterate_upper_bound(upper_bound); + } + + let mut iter = self.db.raw_iterator_opt(read_opts); + iter.seek(prefix); + iter + } + fn find_keys_by_prefix_internal( &self, key_prefix: Vec, ) -> Result>, RocksDbStoreInternalError> { check_key_size(&key_prefix)?; + let mut prefix = self.start_key.clone(); prefix.extend(key_prefix); let len = prefix.len(); - let mut iter = self.db.raw_iterator(); + + let mut iter = self.get_find_prefix_iterator(&prefix); let mut keys = Vec::new(); - iter.seek(&prefix); - let mut next_key = iter.key(); - while let Some(key) = next_key { - if !key.starts_with(&prefix) { - break; - } + while let Some(key) = iter.key() { keys.push(key[len..].to_vec()); iter.next(); - next_key = iter.key(); } Ok(keys) } @@ -198,20 +211,13 @@ impl RocksDbStoreExecutor { let mut prefix = self.start_key.clone(); prefix.extend(key_prefix); let len = prefix.len(); - let mut iter = self.db.raw_iterator(); + + let mut iter = self.get_find_prefix_iterator(&prefix); let mut key_values = Vec::new(); - iter.seek(&prefix); - let mut next_key = iter.key(); - while let Some(key) = next_key { - if !key.starts_with(&prefix) { - break; - } - if let Some(value) = iter.value() { - let key_value = (key[len..].to_vec(), value.to_vec()); - key_values.push(key_value); - } + while let Some((key, value)) = iter.item() { + let key_value = (key[len..].to_vec(), value.to_vec()); + key_values.push(key_value); iter.next(); - next_key = iter.key(); } Ok(key_values) } @@ -373,8 +379,32 @@ impl RocksDbStoreInternal { total_ram / 4, HYPER_CLOCK_CACHE_BLOCK_SIZE, )); + + // Configure bloom filters for prefix iteration optimization + block_options.set_bloom_filter(10.0, false); + block_options.set_whole_key_filtering(false); + + // 32KB blocks instead of default 4KB - reduces iterator seeks + block_options.set_block_size(32 * 1024); + // Use latest format for better compression and performance + block_options.set_format_version(5); + options.set_block_based_table_factory(&block_options); + // Configure prefix extraction for bloom filter optimization + // Use 8 bytes: ROOT_KEY_DOMAIN (1 byte) + BCS variant (1-2 bytes) + identifier start (4-5 bytes) + let prefix_extractor = SliceTransform::create_fixed_prefix(8); + options.set_prefix_extractor(prefix_extractor); + + // 12.5% of memtable size for bloom filter + options.set_memtable_prefix_bloom_ratio(0.125); + // Skip bloom filter for memtable when key exists + options.set_optimize_filters_for_hits(true); + // Use memory-mapped files for faster reads + options.set_allow_mmap_reads(true); + // Don't use random access pattern since we do prefix scans + options.set_advise_random_on_open(false); + let db = DB::open(&options, path_buf)?; let executor = RocksDbStoreExecutor { db: Arc::new(db), From bac34d980716a4cf21da3bbdb8030e5333d63972 Mon Sep 17 00:00:00 2001 From: Andre da Silva <2917611+ndr-ds@users.noreply.github.com> Date: Tue, 16 Sep 2025 18:24:41 -0300 Subject: [PATCH 3/5] Add memory profiling dashboard (#4573) ## Motivation If we want to keep the investigation centered on Grafana, it would be useful to also have a memory profiling dashboard containing the flamegraphs ## Proposal Add a dashboard containing the memory profile flamegraphs to Grafana ## Test Plan Will update with a screenshot ## Release Plan - Nothing to do / These changes follow the usual release cycle. --- .../profiling/jemalloc-memory.json | 146 ++++++++++++++++++ .../grafana-pyroscope-dashboard-config.yaml | 3 +- 2 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 kubernetes/linera-validator/grafana-dashboards/profiling/jemalloc-memory.json diff --git a/kubernetes/linera-validator/grafana-dashboards/profiling/jemalloc-memory.json b/kubernetes/linera-validator/grafana-dashboards/profiling/jemalloc-memory.json new file mode 100644 index 000000000000..c67f579228d0 --- /dev/null +++ b/kubernetes/linera-validator/grafana-dashboards/profiling/jemalloc-memory.json @@ -0,0 +1,146 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "grafana-pyroscope-datasource", + "uid": "P02E4190217B50628" + }, + "gridPos": { + "h": 12, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "displayMode": "flamegraph" + }, + "targets": [ + { + "datasource": { + "type": "grafana-pyroscope-datasource", + "uid": "P02E4190217B50628" + }, + "groupBy": [], + "labelSelector": "{service_name=\"memory/default/shards/\"}", + "profileTypeId": "memory:inuse_space:bytes:space:bytes", + "queryType": "profile", + "refId": "A" + } + ], + "title": "Memory Allocation Flamegraph - Server", + "type": "flamegraph" + }, + { + "datasource": { + "type": "grafana-pyroscope-datasource", + "uid": "${datasource}" + }, + "gridPos": { + "h": 12, + "w": 24, + "x": 0, + "y": 12 + }, + "id": 2, + "options": { + "displayMode": "flamegraph" + }, + "targets": [ + { + "datasource": { + "type": "grafana-pyroscope-datasource", + "uid": "${datasource}" + }, + "groupBy": [], + "labelSelector": "{service_name=\"memory/default/proxy/\"}", + "profileTypeId": "memory:inuse_space:bytes:space:bytes", + "queryType": "profile", + "refId": "A" + } + ], + "title": "Memory Allocation Flamegraph - Proxy", + "type": "flamegraph" + } + ], + "refresh": "30s", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "linera", + "memory", + "profiling" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Pyroscope", + "value": "P02E4190217B50628" + }, + "hide": 0, + "includeAll": false, + "label": "Pyroscope Datasource", + "multi": false, + "name": "datasource", + "options": [], + "query": "grafana-pyroscope-datasource", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "prometheus" + }, + "hide": 0, + "includeAll": false, + "label": "Prometheus Datasource", + "multi": false, + "name": "prometheus_datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Memory", + "uid": "linera-memory-profiling", + "version": 1, + "weekStart": "" +} diff --git a/kubernetes/linera-validator/templates/grafana-pyroscope-dashboard-config.yaml b/kubernetes/linera-validator/templates/grafana-pyroscope-dashboard-config.yaml index 254a6d080c9c..b553a4d713e3 100644 --- a/kubernetes/linera-validator/templates/grafana-pyroscope-dashboard-config.yaml +++ b/kubernetes/linera-validator/templates/grafana-pyroscope-dashboard-config.yaml @@ -7,4 +7,5 @@ metadata: annotations: grafana_folder: "Profiling" data: - cpu_profiling.json: {{ .Files.Get "grafana-dashboards/profiling/cpu.json" | quote }} \ No newline at end of file + cpu_profiling.json: {{ .Files.Get "grafana-dashboards/profiling/cpu.json" | quote }} + memory_profiling.json: {{ .Files.Get "grafana-dashboards/profiling/jemalloc-memory.json" | quote }} \ No newline at end of file From 309828dc104a521aa2b7bfdd695dfdd02ea9b3e0 Mon Sep 17 00:00:00 2001 From: Andre da Silva <2917611+ndr-ds@users.noreply.github.com> Date: Mon, 22 Sep 2025 11:28:22 -0300 Subject: [PATCH 4/5] Adding Tempo for distributed tracing (#4556) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Distributed tracing is a great way to debug different types of issues, including for example latency issues. So this is something we definitely want in general, and probably want by default in production as well. Implement Distributed Tracing using Grafana Tempo. As it is a Grafana product, it integrates well with it, which is great for us. The visualizations also seem to be decent. Deployed a network with this code and the `linera-infra` portion of this, and everything works as expected, and I can see the latency breakdowns (I got a really high latency outlier example): ![Screenshot 2025-09-16 at 13.48.59.png](https://app.graphite.dev/user-attachments/assets/98f49272-d04a-4b7e-aa83-c04f90ec7347.png) I also chose this because it shows we might be waiting in the chain worker channel's queue for a while here 🤔 which might be worth investigating, which I'll do next. - Nothing to do / These changes follow the usual release cycle. --- Cargo.lock | 159 ++++++++++++++++++++-- Cargo.toml | 9 ++ docker/Dockerfile | 2 +- kubernetes/linera-validator/helmfile.yaml | 7 + linera-base/Cargo.toml | 10 ++ linera-base/src/tracing.rs | 133 +++++++++++++++++- linera-service/src/proxy/main.rs | 11 +- linera-service/src/server.rs | 4 +- 8 files changed, 311 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8eeb09ba1b5c..3c74d6f560d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5191,6 +5191,9 @@ dependencies = [ "linera-base", "linera-kywasmtime", "linera-witty", + "opentelemetry", + "opentelemetry-otlp", + "opentelemetry_sdk", "port-selector", "prometheus", "proptest", @@ -5209,6 +5212,7 @@ dependencies = [ "tokio-stream", "tokio-util", "tracing", + "tracing-opentelemetry", "tracing-subscriber 0.3.19", "tracing-web", "trait-variant", @@ -5351,7 +5355,7 @@ dependencies = [ "thiserror 1.0.69", "tokio", "tokio-stream", - "tonic", + "tonic 0.12.3", "tracing", "tracing-subscriber 0.3.19", "trait-set", @@ -5537,7 +5541,7 @@ dependencies = [ "thiserror 1.0.69", "tokio", "tokio-stream", - "tonic", + "tonic 0.12.3", "tonic-build", "tower-http 0.6.6", "tracing", @@ -5690,7 +5694,7 @@ dependencies = [ "thiserror 1.0.69", "tokio", "tokio-util", - "tonic", + "tonic 0.12.3", "tonic-build", "tonic-health", "tonic-reflection", @@ -5831,7 +5835,7 @@ dependencies = [ "tokio-stream", "tokio-util", "toml", - "tonic", + "tonic 0.12.3", "tonic-build", "tonic-health", "tonic-reflection", @@ -5912,7 +5916,7 @@ dependencies = [ "test-strategy", "thiserror 1.0.69", "tokio", - "tonic", + "tonic 0.12.3", "tonic-build", "tracing", "tracing-subscriber 0.3.19", @@ -6884,6 +6888,82 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "opentelemetry" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf416e4cb72756655126f7dd7bb0af49c674f4c1b9903e80c009e0c37e552e6" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.14", + "tracing", +] + +[[package]] +name = "opentelemetry-http" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f6639e842a97dbea8886e3439710ae463120091e2e064518ba8e716e6ac36d" +dependencies = [ + "async-trait", + "bytes", + "http 1.3.1", + "opentelemetry", + "reqwest 0.12.23", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbee664a43e07615731afc539ca60c6d9f1a9425e25ca09c57bc36c87c55852b" +dependencies = [ + "http 1.3.1", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "reqwest 0.12.23", + "thiserror 2.0.14", + "tokio", + "tonic 0.13.1", + "tracing", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e046fd7660710fe5a05e8748e70d9058dc15c94ba914e7c4faa7c728f0e8ddc" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", + "tonic 0.13.1", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f644aa9e5e31d11896e024305d7e3c98a88884d9f8919dbf37a9991bc47a4b" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry", + "percent-encoding", + "rand 0.9.2", + "serde_json", + "thiserror 2.0.14", + "tokio", + "tokio-stream", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -8035,7 +8115,9 @@ checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "base64 0.22.1", "bytes", + "futures-channel", "futures-core", + "futures-util", "http 1.3.1", "http-body 1.0.1", "http-body-util", @@ -10276,6 +10358,34 @@ dependencies = [ "webpki-roots 0.26.11", ] +[[package]] +name = "tonic" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bytes", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-timeout 0.5.2", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "rustls-native-certs 0.8.1", + "tokio", + "tokio-rustls 0.26.2", + "tokio-stream", + "tower 0.5.2", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tonic-build" version = "0.12.3" @@ -10300,7 +10410,7 @@ dependencies = [ "prost", "tokio", "tokio-stream", - "tonic", + "tonic 0.12.3", ] [[package]] @@ -10313,7 +10423,7 @@ dependencies = [ "prost-types", "tokio", "tokio-stream", - "tonic", + "tonic 0.12.3", ] [[package]] @@ -10329,7 +10439,7 @@ dependencies = [ "http-body-util", "pin-project", "tokio-stream", - "tonic", + "tonic 0.12.3", "tower-http 0.5.2", "tower-layer", "tower-service", @@ -10353,7 +10463,7 @@ dependencies = [ "js-sys", "pin-project", "thiserror 1.0.69", - "tonic", + "tonic 0.12.3", "tower-service", "wasm-bindgen", "wasm-bindgen-futures", @@ -10389,7 +10499,9 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", + "indexmap 2.10.0", "pin-project-lite", + "slab", "sync_wrapper 1.0.2", "tokio", "tokio-util", @@ -10499,6 +10611,35 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcf5959f39507d0d04d6413119c04f33b623f4f951ebcbdddddfad2d0623a9c" +dependencies = [ + "js-sys", + "once_cell", + "opentelemetry", + "opentelemetry_sdk", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber 0.3.19", + "web-time", +] + [[package]] name = "tracing-serde" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 7114f247f480..1df68522ad9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -157,6 +157,14 @@ num-format = "0.4.4" num-traits = "0.2.18" octocrab = "0.42.1" oneshot = "0.1.6" +opentelemetry = { version = "0.30.0", features = ["trace"] } +opentelemetry-http = "0.30.0" +opentelemetry-otlp = { version = "0.30.0", features = [ + "grpc-tonic", + "trace", + "tls-roots", +] } +opentelemetry_sdk = { version = "0.30.0", features = ["trace", "rt-tokio"] } papaya = "0.1.5" pathdiff = "0.2.1" port-selector = "0.1.6" @@ -250,6 +258,7 @@ tonic-web-wasm-client = "0.6.0" tower = "0.4.13" tower-http = "0.6.6" tracing = { version = "0.1.40", features = ["release_max_level_debug"] } +tracing-opentelemetry = "0.31.0" tracing-subscriber = { version = "0.3.18", default-features = false, features = [ "env-filter", ] } diff --git a/docker/Dockerfile b/docker/Dockerfile index b233b01346c2..b479cf3cf5a7 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -25,7 +25,7 @@ ARG binaries= ARG copy=${binaries:+_copy} ARG build_flag=--release ARG build_folder=release -ARG build_features=scylladb,metrics,memory-profiling +ARG build_features=scylladb,metrics,memory-profiling,tempo ARG rustflags="-C force-frame-pointers=yes" FROM rust:1.74-slim-bookworm AS builder diff --git a/kubernetes/linera-validator/helmfile.yaml b/kubernetes/linera-validator/helmfile.yaml index beeb057951eb..83f8fa7620c7 100644 --- a/kubernetes/linera-validator/helmfile.yaml +++ b/kubernetes/linera-validator/helmfile.yaml @@ -154,3 +154,10 @@ releases: set: - name: crds.enabled value: "true" + - name: tempo + version: 1.23.3 + namespace: tempo + chart: grafana/tempo + timeout: 900 + values: + - {{ env "LINERA_HELMFILE_VALUES_LINERA_CORE" | default "values-local.yaml.gotmpl" }} diff --git a/linera-base/Cargo.toml b/linera-base/Cargo.toml index 3293bca140ee..dcdd9cd97088 100644 --- a/linera-base/Cargo.toml +++ b/linera-base/Cargo.toml @@ -18,6 +18,12 @@ workspace = true metrics = ["prometheus"] reqwest = ["dep:reqwest"] revm = [] +tempo = [ + "opentelemetry", + "opentelemetry-otlp", + "opentelemetry_sdk", + "tracing-opentelemetry", +] test = ["test-strategy", "proptest"] web = [ "getrandom/js", @@ -80,6 +86,10 @@ tracing-web = { optional = true, workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] chrono.workspace = true +opentelemetry = { workspace = true, optional = true } +opentelemetry-otlp = { workspace = true, optional = true } +opentelemetry_sdk = { workspace = true, optional = true } +tracing-opentelemetry = { workspace = true, optional = true } rand = { workspace = true, features = ["getrandom", "std", "std_rng"] } tokio = { workspace = true, features = [ "process", diff --git a/linera-base/src/tracing.rs b/linera-base/src/tracing.rs index 25358caa7986..fb545dcfdd31 100644 --- a/linera-base/src/tracing.rs +++ b/linera-base/src/tracing.rs @@ -13,6 +13,7 @@ use std::{ use is_terminal::IsTerminal as _; use tracing::Subscriber; use tracing_subscriber::{ + filter::filter_fn, fmt::{ self, format::{FmtSpan, Format, Full}, @@ -23,6 +24,16 @@ use tracing_subscriber::{ registry::LookupSpan, util::SubscriberInitExt, }; +#[cfg(all(not(target_arch = "wasm32"), feature = "tempo"))] +use { + opentelemetry::{global, trace::TracerProvider}, + opentelemetry_otlp::{SpanExporter, WithExportConfig}, + opentelemetry_sdk::{ + trace::{self as sdktrace, SdkTracerProvider}, + Resource, + }, + tracing_opentelemetry::OpenTelemetryLayer, +}; /// Initializes tracing in a standard way. /// @@ -34,6 +45,51 @@ use tracing_subscriber::{ /// store log files. If it is set, a file named `log_name` with the `log` extension is /// created in the directory. pub fn init(log_name: &str) { + init_internal(log_name, false); +} + +/// Initializes tracing with full OpenTelemetry support. +/// +/// **IMPORTANT**: This function must be called from within a Tokio runtime context +/// as it initializes OpenTelemetry background tasks for span batching and export. +/// +/// This sets up complete tracing with OpenTelemetry integration, including the +/// OpenTelemetry layer in the subscriber to export spans to Tempo. +/// +/// ## Span Filtering for Performance +/// +/// By default, spans created by `#[instrument]` are logged to console AND sent +/// to OpenTelemetry. To disable console output for performance-critical functions +/// while keeping OpenTelemetry tracing, use: +/// +/// ```rust +/// use tracing::instrument; +/// +/// #[instrument(target = "telemetry_only")] +/// fn my_performance_critical_function() { +/// // This span will ONLY be sent to OpenTelemetry, not logged to console +/// } +/// ``` +/// +/// All explicit log calls (tracing::info!(), etc.) are always printed regardless +/// of span filtering. +pub async fn init_with_opentelemetry(log_name: &str) { + #[cfg(feature = "tempo")] + { + init_internal(log_name, true); + } + + #[cfg(not(feature = "tempo"))] + { + tracing::warn!( + "OpenTelemetry initialization requested but 'tempo' feature is not enabled. \ + Initializing standard tracing without OpenTelemetry support." + ); + init_internal(log_name, false); + } +} + +fn init_internal(log_name: &str, with_opentelemetry: bool) { let env_filter = tracing_subscriber::EnvFilter::builder() .with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into()) .from_env_lossy(); @@ -43,17 +99,31 @@ pub fn init(log_name: &str) { .map_or(FmtSpan::NONE, |s| fmt_span_from_str(&s)); let format = std::env::var("RUST_LOG_FORMAT").ok(); - let color_output = !std::env::var("NO_COLOR").is_ok_and(|x| !x.is_empty()) && std::io::stderr().is_terminal(); + // Create a filter that: + // 1. Allows all explicit events (tracing::info!(), etc.) + // 2. Allows spans from #[instrument] by default + // 3. Blocks spans ONLY if they have target = "telemetry_only" + let console_filter = filter_fn(|metadata| { + if metadata.is_span() { + // Block spans that explicitly request telemetry-only via target = "telemetry_only" + metadata.target() != "telemetry_only" + } else { + // Always allow explicit log events (tracing::info!(), etc.) + true + } + }); + let stderr_layer = prepare_formatted_layer( format.as_deref(), fmt::layer() .with_span_events(span_events.clone()) .with_writer(std::io::stderr) .with_ansi(color_output), - ); + ) + .with_filter(console_filter.clone()); let maybe_log_file_layer = open_log_file(log_name).map(|file_writer| { prepare_formatted_layer( @@ -63,13 +133,62 @@ pub fn init(log_name: &str) { .with_writer(Arc::new(file_writer)) .with_ansi(false), ) + .with_filter(console_filter.clone()) }); - tracing_subscriber::registry() - .with(env_filter) - .with(maybe_log_file_layer) - .with(stderr_layer) - .init(); + #[cfg(any(target_arch = "wasm32", not(feature = "tempo")))] + { + let _ = with_opentelemetry; + tracing_subscriber::registry() + .with(env_filter) + .with(maybe_log_file_layer) + .with(stderr_layer) + .init(); + } + + #[cfg(all(not(target_arch = "wasm32"), feature = "tempo"))] + { + if with_opentelemetry { + // Initialize OpenTelemetry within async context + let otlp_endpoint = std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT") + .unwrap_or_else(|_| "http://tempo.tempo.svc.cluster.local:4317".to_string()); + + let exporter = SpanExporter::builder() + .with_tonic() + .with_endpoint(otlp_endpoint) + .build() + .expect("Failed to create OTLP exporter"); + + let resource = Resource::builder() + .with_service_name(log_name.to_string()) + .build(); + + let tracer_provider = SdkTracerProvider::builder() + .with_resource(resource) + .with_batch_exporter(exporter) + .with_sampler(sdktrace::Sampler::AlwaysOn) + .build(); + + // Set the global tracer provider + global::set_tracer_provider(tracer_provider.clone()); + + let tracer = tracer_provider.tracer("linera"); + let opentelemetry_layer = OpenTelemetryLayer::new(tracer); + + tracing_subscriber::registry() + .with(env_filter) + .with(maybe_log_file_layer) + .with(stderr_layer) + .with(opentelemetry_layer) + .init(); + } else { + tracing_subscriber::registry() + .with(env_filter) + .with(maybe_log_file_layer) + .with(stderr_layer) + .init(); + } + } } /// Opens a log file for writing. diff --git a/linera-service/src/proxy/main.rs b/linera-service/src/proxy/main.rs index 825bfebcd956..0c0a54148068 100644 --- a/linera-service/src/proxy/main.rs +++ b/linera-service/src/proxy/main.rs @@ -459,11 +459,6 @@ where fn main() -> Result<()> { let options = ::parse(); - let server_config: ValidatorServerConfig = - util::read_json(&options.config_path).expect("Fail to read server config"); - let public_key = &server_config.validator.public_key; - - linera_base::tracing::init(&format!("validator-{public_key}-proxy")); let mut runtime = if options.tokio_threads == Some(1) { tokio::runtime::Builder::new_current_thread() @@ -486,6 +481,12 @@ fn main() -> Result<()> { impl ProxyOptions { async fn run(&self) -> Result<()> { + let server_config: ValidatorServerConfig = + util::read_json(&self.config_path).expect("Fail to read server config"); + let public_key = &server_config.validator.public_key; + linera_base::tracing::init_with_opentelemetry(&format!("validator-{public_key}-proxy")) + .await; + let store_config = self .storage_config .add_common_storage_options(&self.common_storage_options)?; diff --git a/linera-service/src/server.rs b/linera-service/src/server.rs index fbccf7683aa5..f7641883bd42 100644 --- a/linera-service/src/server.rs +++ b/linera-service/src/server.rs @@ -432,8 +432,6 @@ enum ServerCommand { fn main() { let options = ::parse(); - linera_base::tracing::init(&log_file_name_for(&options.command)); - let mut runtime = if options.tokio_threads == Some(1) { tokio::runtime::Builder::new_current_thread() } else { @@ -481,6 +479,8 @@ fn log_file_name_for(command: &ServerCommand) -> Cow<'static, str> { } async fn run(options: ServerOptions) { + linera_base::tracing::init_with_opentelemetry(&log_file_name_for(&options.command)).await; + match options.command { ServerCommand::Run { server_config_path, From 84aecfb2880200e441311ff8d1f23f817b7892b2 Mon Sep 17 00:00:00 2001 From: Andre da Silva <2917611+ndr-ds@users.noreply.github.com> Date: Mon, 22 Sep 2025 16:58:42 -0300 Subject: [PATCH 5/5] Add more instrumentation (#4555) Now that we have distributed tracing (after https://github.com/linera-io/linera-protocol/pull/4556), we need more instrumentation so we have data about more functions in the breakdowns. Instrument more functions with `telemetry_only` so that we don't get spammed in our logs, but the spans still get sent to Tempo. Tested this with https://github.com/linera-io/linera-protocol/pull/4556, saw the spans properly show in the breakdowns. - Nothing to do / These changes follow the usual release cycle. --- Cargo.lock | 1 + examples/Cargo.lock | 1 + linera-base/src/tracing.rs | 63 +++++++------ linera-chain/src/chain.rs | 53 +++++++++++ linera-core/src/chain_worker/actor.rs | 9 +- linera-core/src/chain_worker/state.rs | 97 +++++++++++++++++++- linera-core/src/worker.rs | 72 ++++++++++++--- linera-service/src/proxy/grpc.rs | 123 ++++++++++++++++++++++---- linera-storage/Cargo.toml | 1 + linera-storage/src/db_storage.rs | 8 ++ 10 files changed, 365 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c74d6f560d8..b7d8d9bb3f21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5891,6 +5891,7 @@ dependencies = [ "papaya", "prometheus", "serde", + "tracing", ] [[package]] diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 09c86cf1e7a4..9e141c6907f8 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -3901,6 +3901,7 @@ dependencies = [ "papaya", "prometheus", "serde", + "tracing", ] [[package]] diff --git a/linera-base/src/tracing.rs b/linera-base/src/tracing.rs index fb545dcfdd31..09811bb0f8eb 100644 --- a/linera-base/src/tracing.rs +++ b/linera-base/src/tracing.rs @@ -12,8 +12,9 @@ use std::{ use is_terminal::IsTerminal as _; use tracing::Subscriber; +#[cfg(all(not(target_arch = "wasm32"), feature = "tempo"))] +use tracing_subscriber::filter::{filter_fn, FilterExt as _}; use tracing_subscriber::{ - filter::filter_fn, fmt::{ self, format::{FmtSpan, Format, Full}, @@ -59,20 +60,33 @@ pub fn init(log_name: &str) { /// ## Span Filtering for Performance /// /// By default, spans created by `#[instrument]` are logged to console AND sent -/// to OpenTelemetry. To disable console output for performance-critical functions -/// while keeping OpenTelemetry tracing, use: +/// to OpenTelemetry. In order to not spam stderr, you can set a low level, and use the +/// `telemetry_only` target: /// /// ```rust -/// use tracing::instrument; +/// use tracing::{instrument, Level}; +/// +/// // Always sent to telemetry; console output controlled by level +/// #[instrument(level = "trace", target = "telemetry_only")] +/// fn my_called_too_frequently_function() { +/// // Will be sent to OpenTelemetry regardless of RUST_LOG level +/// // Will only appear in console if RUST_LOG includes trace level +/// } /// -/// #[instrument(target = "telemetry_only")] -/// fn my_performance_critical_function() { -/// // This span will ONLY be sent to OpenTelemetry, not logged to console +/// // Higher level - more likely to appear in console +/// #[instrument(level = "info", target = "telemetry_only")] +/// fn my_important_function() { +/// // Will be sent to OpenTelemetry regardless of RUST_LOG level +/// // Will appear in console if RUST_LOG includes info level or higher /// } /// ``` /// -/// All explicit log calls (tracing::info!(), etc.) are always printed regardless -/// of span filtering. +/// **Key behaviors:** +/// - If span level >= RUST_LOG level: span goes to BOTH telemetry AND console (regardless of target) +/// - If span level < RUST_LOG level AND target = "telemetry_only": span goes to telemetry ONLY +/// - If span level < RUST_LOG level AND target != "telemetry_only": span is filtered out completely +/// - Default level for `telemetry_only` should be `trace` for minimal console noise +/// - All explicit log calls (tracing::info!(), etc.) are always printed regardless of span filtering pub async fn init_with_opentelemetry(log_name: &str) { #[cfg(feature = "tempo")] { @@ -102,28 +116,13 @@ fn init_internal(log_name: &str, with_opentelemetry: bool) { let color_output = !std::env::var("NO_COLOR").is_ok_and(|x| !x.is_empty()) && std::io::stderr().is_terminal(); - // Create a filter that: - // 1. Allows all explicit events (tracing::info!(), etc.) - // 2. Allows spans from #[instrument] by default - // 3. Blocks spans ONLY if they have target = "telemetry_only" - let console_filter = filter_fn(|metadata| { - if metadata.is_span() { - // Block spans that explicitly request telemetry-only via target = "telemetry_only" - metadata.target() != "telemetry_only" - } else { - // Always allow explicit log events (tracing::info!(), etc.) - true - } - }); - let stderr_layer = prepare_formatted_layer( format.as_deref(), fmt::layer() .with_span_events(span_events.clone()) .with_writer(std::io::stderr) .with_ansi(color_output), - ) - .with_filter(console_filter.clone()); + ); let maybe_log_file_layer = open_log_file(log_name).map(|file_writer| { prepare_formatted_layer( @@ -133,7 +132,6 @@ fn init_internal(log_name: &str, with_opentelemetry: bool) { .with_writer(Arc::new(file_writer)) .with_ansi(false), ) - .with_filter(console_filter.clone()) }); #[cfg(any(target_arch = "wasm32", not(feature = "tempo")))] @@ -173,7 +171,18 @@ fn init_internal(log_name: &str, with_opentelemetry: bool) { global::set_tracer_provider(tracer_provider.clone()); let tracer = tracer_provider.tracer("linera"); - let opentelemetry_layer = OpenTelemetryLayer::new(tracer); + + let telemetry_only_filter = + filter_fn(|metadata| metadata.is_span() && metadata.target() == "telemetry_only"); + + let otel_env_filter = tracing_subscriber::EnvFilter::builder() + .with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into()) + .from_env_lossy(); + + let opentelemetry_filter = otel_env_filter.or(telemetry_only_filter); + + let opentelemetry_layer = + OpenTelemetryLayer::new(tracer).with_filter(opentelemetry_filter); tracing_subscriber::registry() .with(env_filter) diff --git a/linera-chain/src/chain.rs b/linera-chain/src/chain.rs index a644e13cb73a..ca56d67924a3 100644 --- a/linera-chain/src/chain.rs +++ b/linera-chain/src/chain.rs @@ -35,6 +35,7 @@ use linera_views::{ views::{ClonableView, CryptoHashView, RootView, View}, }; use serde::{Deserialize, Serialize}; +use tracing::instrument; use crate::{ block::{Block, ConfirmedBlock}, @@ -388,6 +389,9 @@ where self.context().extra().chain_id() } + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + ))] pub async fn query_application( &mut self, local_time: Timestamp, @@ -405,6 +409,10 @@ where .with_execution_context(ChainExecutionContext::Query) } + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + application_id = %application_id + ))] pub async fn describe_application( &mut self, application_id: ApplicationId, @@ -416,6 +424,11 @@ where .with_execution_context(ChainExecutionContext::DescribeApplication) } + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + target = %target, + height = %height + ))] pub async fn mark_messages_as_received( &mut self, target: &ChainId, @@ -503,6 +516,9 @@ where /// Verifies that this chain is up-to-date and all the messages executed ahead of time /// have been properly received by now. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id() + ))] pub async fn validate_incoming_bundles(&self) -> Result<(), ChainError> { let chain_id = self.chain_id(); let pairs = self.inboxes.try_load_all_entries().await?; @@ -564,6 +580,11 @@ where /// round timeouts. /// /// Returns `true` if incoming `Subscribe` messages created new outbox entries. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + origin = %origin, + bundle_height = %bundle.height + ))] pub async fn receive_message_bundle( &mut self, origin: &ChainId, @@ -658,6 +679,9 @@ where } /// Removes the incoming message bundles in the block from the inboxes. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + ))] pub async fn remove_bundles_from_inboxes( &mut self, timestamp: Timestamp, @@ -747,6 +771,10 @@ where /// Executes a block: first the incoming messages, then the main operation. /// Does not update chain state other than the execution state. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %block.chain_id, + block_height = %block.height + ))] #[expect(clippy::too_many_arguments)] async fn execute_block_inner( chain: &mut ExecutionStateView, @@ -856,6 +884,10 @@ where /// Executes a block: first the incoming messages, then the main operation. /// Does not update chain state other than the execution state. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + block_height = %block.height + ))] pub async fn execute_block( &mut self, block: &ProposedBlock, @@ -915,6 +947,11 @@ where /// Applies an execution outcome to the chain, updating the outboxes, state hash and chain /// manager. This does not touch the execution state itself, which must be updated separately. + /// Returns the set of event streams that were updated as a result of applying the block. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + block_height = %block.inner().inner().header.height + ))] pub async fn apply_confirmed_block( &mut self, block: &ConfirmedBlock, @@ -947,6 +984,10 @@ where } /// Adds a block to `preprocessed_blocks`, and updates the outboxes where possible. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + block_height = %block.inner().inner().header.height + ))] pub async fn preprocess_block(&mut self, block: &ConfirmedBlock) -> Result<(), ChainError> { let hash = block.inner().hash(); let block = block.inner().inner(); @@ -969,6 +1010,10 @@ where } /// Verifies that the block is valid according to the chain's application permission settings. + #[instrument(target = "telemetry_only", skip_all, fields( + block_height = %block.height, + num_transactions = %block.transactions.len() + ))] fn check_app_permissions( app_permissions: &ApplicationPermissions, block: &ProposedBlock, @@ -1011,6 +1056,10 @@ where } /// Returns the hashes of all blocks we have in the given range. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + next_block_height = %self.tip_state.get().next_block_height + ))] pub async fn block_hashes( &self, range: impl RangeBounds, @@ -1056,6 +1105,10 @@ where /// Updates the outboxes with the messages sent in the block. /// /// Returns the set of all recipients. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + block_height = %block.header.height + ))] async fn process_outgoing_messages( &mut self, block: &Block, diff --git a/linera-core/src/chain_worker/actor.rs b/linera-core/src/chain_worker/actor.rs index 83c196954021..0170aabe6abb 100644 --- a/linera-core/src/chain_worker/actor.rs +++ b/linera-core/src/chain_worker/actor.rs @@ -244,7 +244,7 @@ where /// Runs the worker until there are no more incoming requests. #[instrument( skip_all, - fields(chain_id = format!("{:.8}", self.chain_id)), + fields(chain_id = format!("{:.8}", self.chain_id), long_lived_services = %self.config.long_lived_services), )] async fn handle_requests( self, @@ -276,9 +276,12 @@ where self.chain_id, service_runtime_endpoint, ) + .instrument(span.clone()) .await?; - Box::pin(worker.handle_request(request).instrument(span)).await; + Box::pin(worker.handle_request(request)) + .instrument(span) + .await; loop { futures::select! { @@ -287,7 +290,7 @@ where let Some((request, span)) = maybe_request else { break; // Request sender was dropped. }; - Box::pin(worker.handle_request(request).instrument(span)).await; + Box::pin(worker.handle_request(request)).instrument(span).await; } } } diff --git a/linera-core/src/chain_worker/state.rs b/linera-core/src/chain_worker/state.rs index 0d0ba688011f..36a18ade8fd7 100644 --- a/linera-core/src/chain_worker/state.rs +++ b/linera-core/src/chain_worker/state.rs @@ -70,6 +70,9 @@ where StorageClient: Storage + Clone + Send + Sync + 'static, { /// Creates a new [`ChainWorkerState`] using the provided `storage` client. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %chain_id + ))] #[expect(clippy::too_many_arguments)] pub async fn load( config: ChainWorkerConfig, @@ -103,7 +106,7 @@ where } /// Handles a request and applies it to the chain state. - #[instrument(skip_all)] + #[instrument(skip_all, fields(chain_id = %self.chain_id()))] pub async fn handle_request(&mut self, request: ChainWorkerRequest) { tracing::trace!("Handling chain worker request: {request:?}"); // TODO(#2237): Spawn concurrent tasks for read-only operations @@ -264,6 +267,10 @@ where } /// Returns the requested blob, if it belongs to the current locking block or pending proposal. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + blob_id = %blob_id + ))] pub(super) async fn download_pending_blob(&self, blob_id: BlobId) -> Result { if let Some(blob) = self.chain.manager.pending_blob(&blob_id).await? { return Ok(blob); @@ -274,6 +281,9 @@ where /// Reads the blobs from the chain manager or from storage. Returns an error if any are /// missing. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id() + ))] async fn get_required_blobs( &self, required_blob_ids: impl IntoIterator, @@ -294,6 +304,9 @@ where } /// Tries to read the blobs from the chain manager or storage. Returns `None` if not found. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id() + ))] async fn maybe_get_required_blobs( &self, blob_ids: impl IntoIterator, @@ -362,6 +375,9 @@ where } /// Loads pending cross-chain requests, and adds `NewRound` notifications where appropriate. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id() + ))] async fn create_network_actions( &self, old_round: Option, @@ -399,6 +415,10 @@ where }) } + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + num_recipients = %heights_by_recipient.len() + ))] async fn create_cross_chain_requests( &self, heights_by_recipient: BTreeMap>, @@ -470,6 +490,10 @@ where /// Returns true if there are no more outgoing messages in flight up to the given /// block height. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + height = %height + ))] pub async fn all_messages_to_tracked_chains_delivered_up_to( &self, height: BlockHeight, @@ -496,6 +520,10 @@ where } /// Processes a leader timeout issued for this multi-owner chain. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + height = %certificate.inner().height() + ))] pub(super) async fn process_timeout( &mut self, certificate: TimeoutCertificate, @@ -534,6 +562,10 @@ where /// /// If they cannot be found, it creates an entry in `pending_proposed_blobs` so they can be /// submitted one by one. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + block_height = %proposal.content.block.height + ))] pub(super) async fn load_proposal_blobs( &mut self, proposal: &BlockProposal, @@ -578,6 +610,10 @@ where } /// Processes a validated block issued for this multi-owner chain. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + block_height = %certificate.block().header.height + ))] pub(super) async fn process_validated_block( &mut self, certificate: ValidatedBlockCertificate, @@ -635,6 +671,11 @@ where } /// Processes a confirmed block (aka a commit). + #[instrument(skip_all, fields( + chain_id = %certificate.block().header.chain_id, + height = %certificate.block().header.height, + block_hash = %certificate.hash(), + ))] pub(super) async fn process_confirmed_block( &mut self, certificate: ConfirmedBlockCertificate, @@ -802,6 +843,7 @@ where computed: Box::new(verified_outcome), } ); + // Update the rest of the chain state. chain .apply_confirmed_block(certificate.value(), local_time) @@ -866,7 +908,7 @@ where } /// Updates the chain's inboxes, receiving messages from a cross-chain update. - #[instrument(level = "trace", skip(self, bundles))] + #[instrument(level = "trace", target = "telemetry_only", skip(self, bundles))] pub(super) async fn process_cross_chain_update( &mut self, origin: ChainId, @@ -915,6 +957,11 @@ where } /// Handles the cross-chain request confirming that the recipient was updated. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + recipient = %recipient, + latest_height = %latest_height + ))] pub(super) async fn confirm_updated_recipient( &mut self, recipient: ChainId, @@ -937,6 +984,10 @@ where Ok(()) } + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + num_trackers = %new_trackers.len() + ))] pub async fn update_received_certificate_trackers( &mut self, new_trackers: BTreeMap, @@ -948,6 +999,11 @@ where } /// Attempts to vote for a leader timeout, if possible. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + height = %height, + round = %round + ))] pub(super) async fn vote_for_leader_timeout( &mut self, height: BlockHeight, @@ -975,6 +1031,9 @@ where } /// Votes for falling back to a public chain. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id() + ))] pub(super) async fn vote_for_fallback(&mut self) -> Result<(), WorkerError> { let chain = &mut self.chain; if let (epoch, Some(entry)) = ( @@ -997,6 +1056,10 @@ where Ok(()) } + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + blob_id = %blob.id() + ))] pub(super) async fn handle_pending_blob( &mut self, blob: Blob, @@ -1033,6 +1096,10 @@ where /// Returns a stored [`Certificate`] for the chain's block at the requested [`BlockHeight`]. #[cfg(with_testing)] + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + height = %height + ))] pub(super) async fn read_certificate( &mut self, height: BlockHeight, @@ -1051,6 +1118,10 @@ where } /// Queries an application's state on the chain. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + query_application_id = %query.application_id() + ))] pub(super) async fn query_application( &mut self, query: Query, @@ -1065,6 +1136,10 @@ where } /// Returns an application's description. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + application_id = %application_id + ))] pub(super) async fn describe_application( &mut self, application_id: ApplicationId, @@ -1075,6 +1150,10 @@ where } /// Executes a block without persisting any changes to the state. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + block_height = %block.height + ))] pub(super) async fn stage_block_execution( &mut self, block: ProposedBlock, @@ -1107,6 +1186,10 @@ where } /// Validates and executes a block proposed to extend this chain. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + block_height = %proposal.content.block.height + ))] pub(super) async fn handle_block_proposal( &mut self, proposal: BlockProposal, @@ -1250,6 +1333,9 @@ where } /// Prepares a [`ChainInfoResponse`] for a [`ChainInfoQuery`]. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id() + ))] pub(super) async fn prepare_chain_info_response( &mut self, query: ChainInfoQuery, @@ -1315,6 +1401,10 @@ where } /// Executes a block, caches the result, and returns the outcome. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id(), + block_height = %block.height + ))] async fn execute_block( &mut self, block: &ProposedBlock, @@ -1356,6 +1446,9 @@ where /// Stores the chain state in persistent storage. /// /// Waits until the [`ChainStateView`] is no longer shared before persisting the changes. + #[instrument(target = "telemetry_only", skip_all, fields( + chain_id = %self.chain_id() + ))] async fn save(&mut self) -> Result<(), WorkerError> { self.clear_shared_chain_view().await; self.chain.save().await?; diff --git a/linera-core/src/worker.rs b/linera-core/src/worker.rs index 2bf504e81723..5e5b66d868c9 100644 --- a/linera-core/src/worker.rs +++ b/linera-core/src/worker.rs @@ -457,7 +457,7 @@ where .ok_or(WorkerError::InvalidLiteCertificate)?, )) } - _ => return Err(WorkerError::InvalidLiteCertificate), + _ => Err(WorkerError::InvalidLiteCertificate), } } } @@ -550,7 +550,11 @@ where } /// Executes a [`Query`] for an application's state on a specific chain. - #[instrument(level = "trace", skip(self, chain_id, query))] + #[instrument( + level = "trace", + target = "telemetry_only", + skip(self, chain_id, query) + )] pub async fn query_application( &self, chain_id: ChainId, @@ -562,7 +566,11 @@ where .await } - #[instrument(level = "trace", skip(self, chain_id, application_id))] + #[instrument(level = "trace", target = "telemetry_only", skip(self, chain_id, application_id), fields( + nickname = %self.nickname, + chain_id = %chain_id, + application_id = %application_id + ))] pub async fn describe_application( &self, chain_id: ChainId, @@ -580,7 +588,13 @@ where /// Processes a confirmed block (aka a commit). #[instrument( level = "trace", - skip(self, certificate, notify_when_messages_are_delivered) + target = "telemetry_only", + skip(self, certificate, notify_when_messages_are_delivered), + fields( + nickname = %self.nickname, + chain_id = %certificate.block().header.chain_id, + block_height = %certificate.block().header.height + ) )] async fn process_confirmed_block( &self, @@ -599,7 +613,11 @@ where } /// Processes a validated block issued from a multi-owner chain. - #[instrument(level = "trace", skip(self, certificate))] + #[instrument(level = "trace", target = "telemetry_only", skip(self, certificate), fields( + nickname = %self.nickname, + chain_id = %certificate.block().header.chain_id, + block_height = %certificate.block().header.height + ))] async fn process_validated_block( &self, certificate: ValidatedBlockCertificate, @@ -615,7 +633,11 @@ where } /// Processes a leader timeout issued from a multi-owner chain. - #[instrument(level = "trace", skip(self, certificate))] + #[instrument(level = "trace", target = "telemetry_only", skip(self, certificate), fields( + nickname = %self.nickname, + chain_id = %certificate.value().chain_id(), + height = %certificate.value().height() + ))] async fn process_timeout( &self, certificate: TimeoutCertificate, @@ -630,7 +652,12 @@ where .await } - #[instrument(level = "trace", skip(self, origin, recipient, bundles))] + #[instrument(level = "trace", target = "telemetry_only", skip(self, origin, recipient, bundles), fields( + nickname = %self.nickname, + origin = %origin, + recipient = %recipient, + num_bundles = %bundles.len() + ))] async fn process_cross_chain_update( &self, origin: ChainId, @@ -648,7 +675,11 @@ where } /// Returns a stored [`ConfirmedBlockCertificate`] for a chain's block. - #[instrument(level = "trace", skip(self, chain_id, height))] + #[instrument(level = "trace", target = "telemetry_only", skip(self, chain_id, height), fields( + nickname = %self.nickname, + chain_id = %chain_id, + height = %height + ))] #[cfg(with_testing)] pub async fn read_certificate( &self, @@ -666,7 +697,10 @@ where /// /// The returned view holds a lock on the chain state, which prevents the worker from changing /// the state of that chain. - #[instrument(level = "trace", skip(self))] + #[instrument(level = "trace", target = "telemetry_only", skip(self), fields( + nickname = %self.nickname, + chain_id = %chain_id + ))] pub async fn chain_state_view( &self, chain_id: ChainId, @@ -677,7 +711,10 @@ where .await } - #[instrument(level = "trace", skip(self, request_builder))] + #[instrument(level = "trace", target = "telemetry_only", skip(self, request_builder), fields( + nickname = %self.nickname, + chain_id = %chain_id + ))] /// Sends a request to the [`ChainWorker`] for a [`ChainId`] and waits for the `Response`. async fn query_chain_worker( &self, @@ -700,7 +737,10 @@ where /// Retrieves an endpoint to a [`ChainWorkerActor`] from the cache, creating one and adding it /// to the cache if needed. - #[instrument(level = "trace", skip(self))] + #[instrument(level = "trace", target = "telemetry_only", skip(self), fields( + nickname = %self.nickname, + chain_id = %chain_id + ))] async fn get_chain_worker_endpoint( &self, chain_id: ChainId, @@ -749,7 +789,10 @@ where /// and add it to the cache if needed. /// /// Returns [`None`] if the cache is full and no candidate for eviction was found. - #[instrument(level = "trace", skip(self))] + #[instrument(level = "trace", target = "telemetry_only", skip(self), fields( + nickname = %self.nickname, + chain_id = %chain_id + ))] #[expect(clippy::type_complexity)] fn try_get_chain_worker_endpoint( &self, @@ -1062,6 +1105,11 @@ where } /// Updates the received certificate trackers to at least the given values. + #[instrument(target = "telemetry_only", skip_all, fields( + nickname = %self.nickname, + chain_id = %chain_id, + num_trackers = %new_trackers.len() + ))] pub async fn update_received_certificate_trackers( &self, chain_id: ChainId, diff --git a/linera-service/src/proxy/grpc.rs b/linera-service/src/proxy/grpc.rs index 9663e5c811ef..03aca53202b9 100644 --- a/linera-service/src/proxy/grpc.rs +++ b/linera-service/src/proxy/grpc.rs @@ -302,6 +302,7 @@ where } #[allow(clippy::result_large_err)] + #[instrument(target = "telemetry_only", skip_all, fields(remote_addr = ?request.remote_addr(), chain_id = ?request.get_ref().chain_id()))] fn worker_client( &self, request: Request, @@ -373,7 +374,12 @@ where { type SubscribeStream = UnboundedReceiverStream>; - #[instrument(skip_all, err(Display))] + #[instrument( + target = "telemetry_only", + skip_all, + err(Display), + fields(method = "handle_block_proposal") + )] async fn handle_block_proposal( &self, request: Request, @@ -385,7 +391,12 @@ where ) } - #[instrument(skip_all, err(Display))] + #[instrument( + target = "telemetry_only", + skip_all, + err(Display), + fields(method = "handle_lite_certificate") + )] async fn handle_lite_certificate( &self, request: Request, @@ -397,7 +408,12 @@ where ) } - #[instrument(skip_all, err(Display))] + #[instrument( + target = "telemetry_only", + skip_all, + err(Display), + fields(method = "handle_confirmed_certificate") + )] async fn handle_confirmed_certificate( &self, request: Request, @@ -409,7 +425,12 @@ where ) } - #[instrument(skip_all, err(Display))] + #[instrument( + target = "telemetry_only", + skip_all, + err(Display), + fields(method = "handle_validated_certificate") + )] async fn handle_validated_certificate( &self, request: Request, @@ -421,7 +442,12 @@ where ) } - #[instrument(skip_all, err(Display))] + #[instrument( + target = "telemetry_only", + skip_all, + err(Display), + fields(method = "handle_timeout_certificate") + )] async fn handle_timeout_certificate( &self, request: Request, @@ -433,7 +459,12 @@ where ) } - #[instrument(skip_all, err(Display))] + #[instrument( + target = "telemetry_only", + skip_all, + err(Display), + fields(method = "handle_chain_info_query") + )] async fn handle_chain_info_query( &self, request: Request, @@ -445,7 +476,12 @@ where ) } - #[instrument(skip_all, err(Display))] + #[instrument( + target = "telemetry_only", + skip_all, + err(Display), + fields(method = "subscribe") + )] async fn subscribe( &self, request: Request, @@ -474,7 +510,12 @@ where Ok(Response::new(linera_version::VersionInfo::default().into())) } - #[instrument(skip_all, err(Display))] + #[instrument( + target = "telemetry_only", + skip_all, + err(Display), + fields(method = "get_network_description") + )] async fn get_network_description( &self, _request: Request<()>, @@ -489,7 +530,12 @@ where Ok(Response::new(description.into())) } - #[instrument(skip_all, err(Display))] + #[instrument( + target = "telemetry_only", + skip_all, + err(Display), + fields(method = "upload_blob") + )] async fn upload_blob(&self, request: Request) -> Result, Status> { let content: linera_sdk::linera_base_types::BlobContent = request.into_inner().try_into()?; @@ -502,7 +548,12 @@ where Ok(Response::new(id.try_into()?)) } - #[instrument(skip_all, err(Display))] + #[instrument( + target = "telemetry_only", + skip_all, + err(Display), + fields(method = "download_blob") + )] async fn download_blob( &self, request: Request, @@ -518,7 +569,12 @@ where Ok(Response::new(blob.into_content().try_into()?)) } - #[instrument(skip_all, err(Display))] + #[instrument( + target = "telemetry_only", + skip_all, + err(Display), + fields(method = "download_pending_blob") + )] async fn download_pending_blob( &self, request: Request, @@ -543,7 +599,12 @@ where } } - #[instrument(skip_all, err(Display))] + #[instrument( + target = "telemetry_only", + skip_all, + err(Display), + fields(method = "handle_pending_blob") + )] async fn handle_pending_blob( &self, request: Request, @@ -568,7 +629,12 @@ where } } - #[instrument(skip_all, err(Display))] + #[instrument( + target = "telemetry_only", + skip_all, + err(Display), + fields(method = "download_certificate") + )] async fn download_certificate( &self, request: Request, @@ -585,7 +651,12 @@ where Ok(Response::new(certificate.try_into()?)) } - #[instrument(skip_all, err(Display))] + #[instrument( + target = "telemetry_only", + skip_all, + err(Display), + fields(method = "download_certificates") + )] async fn download_certificates( &self, request: Request, @@ -631,7 +702,12 @@ where )?)) } - #[instrument(skip_all, err(Display))] + #[instrument( + target = "telemetry_only", + skip_all, + err(Display), + fields(method = "download_certificates_by_heights") + )] async fn download_certificates_by_heights( &self, request: Request, @@ -746,7 +822,9 @@ where })) } - #[instrument(skip_all, err(level = Level::WARN))] + #[instrument(target = "telemetry_only", skip_all, err(level = Level::WARN), fields( + method = "blob_last_used_by" + ))] async fn blob_last_used_by( &self, request: Request, @@ -766,7 +844,7 @@ where Ok(Response::new(last_used_by.into())) } - #[instrument(skip_all, err(level = Level::WARN))] + #[instrument(target = "telemetry_only", skip_all, err(level = Level::WARN))] async fn missing_blob_ids( &self, request: Request, @@ -781,7 +859,9 @@ where Ok(Response::new(missing_blob_ids.try_into()?)) } - #[instrument(skip_all, err(level = Level::WARN))] + #[instrument(target = "telemetry_only", skip_all, err(level = Level::WARN), fields( + method = "blob_last_used_by_certificate" + ))] async fn blob_last_used_by_certificate( &self, request: Request, @@ -797,7 +877,12 @@ impl NotifierService for GrpcProxy where S: Storage + Clone + Send + Sync + 'static, { - #[instrument(skip_all, err(Display))] + #[instrument( + target = "telemetry_only", + skip_all, + err(Display), + fields(method = "notify") + )] async fn notify(&self, request: Request) -> Result, Status> { let notification = request.into_inner(); let chain_id = notification diff --git a/linera-storage/Cargo.toml b/linera-storage/Cargo.toml index 04ff5dd63209..02c34f4ac613 100644 --- a/linera-storage/Cargo.toml +++ b/linera-storage/Cargo.toml @@ -45,6 +45,7 @@ linera-views.workspace = true papaya.workspace = true prometheus.workspace = true serde.workspace = true +tracing.workspace = true [dev-dependencies] anyhow.workspace = true diff --git a/linera-storage/src/db_storage.rs b/linera-storage/src/db_storage.rs index e34562657c44..5c0267556eb2 100644 --- a/linera-storage/src/db_storage.rs +++ b/linera-storage/src/db_storage.rs @@ -28,6 +28,7 @@ use linera_views::{ ViewError, }; use serde::{Deserialize, Serialize}; +use tracing::instrument; #[cfg(with_testing)] use { futures::channel::oneshot::{self, Receiver}, @@ -544,6 +545,7 @@ where &self.clock } + #[instrument(level = "trace", target = "telemetry_only", skip_all, fields(chain_id = %chain_id))] async fn load_chain( &self, chain_id: ChainId, @@ -563,6 +565,7 @@ where ChainStateView::load(context).await } + #[instrument(level = "trace", target = "telemetry_only", skip_all, fields(blob_id = %blob_id))] async fn contains_blob(&self, blob_id: BlobId) -> Result { let store = self.database.open_shared(&[])?; let blob_key = bcs::to_bytes(&BaseKey::Blob(blob_id))?; @@ -572,6 +575,7 @@ where Ok(test) } + #[instrument(target = "telemetry_only", skip_all, fields(blob_count = blob_ids.len()))] async fn missing_blobs(&self, blob_ids: &[BlobId]) -> Result, ViewError> { let store = self.database.open_shared(&[])?; let mut keys = Vec::new(); @@ -602,6 +606,7 @@ where Ok(test) } + #[instrument(target = "telemetry_only", skip_all, fields(hash = %hash))] async fn read_confirmed_block( &self, hash: CryptoHash, @@ -616,6 +621,7 @@ where Ok(value) } + #[instrument(target = "telemetry_only", skip_all, fields(blob_id = %blob_id))] async fn read_blob(&self, blob_id: BlobId) -> Result, ViewError> { let store = self.database.open_shared(&[])?; let blob_key = bcs::to_bytes(&BaseKey::Blob(blob_id))?; @@ -682,6 +688,7 @@ where Ok(blob_states) } + #[instrument(target = "telemetry_only", skip_all, fields(blob_id = %blob.id()))] async fn write_blob(&self, blob: &Blob) -> Result<(), ViewError> { let mut batch = Batch::new(); batch.add_blob(blob)?; @@ -996,6 +1003,7 @@ where Ok(()) } + #[instrument(target = "telemetry_only", skip_all, fields(batch_size = batch.key_value_bytes.len()))] async fn write_batch(&self, batch: Batch) -> Result<(), ViewError> { if batch.key_value_bytes.is_empty() { return Ok(());