polkadot_node_jaeger/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Polkadot Jaeger related primitives
18//!
19//! Provides primitives used by Polkadot for interfacing with Jaeger.
20//!
21//! # Integration
22//!
23//! See <https://www.jaegertracing.io/> for an introduction.
24//!
25//! The easiest way to try Jaeger is:
26//!
27//! - Start a docker container with the all-in-one docker image (see below).
28//! - Open your browser and navigate to <https://localhost:16686> to access the UI.
29//!
30//! The all-in-one image can be started with:
31//!
32//! ```not_rust
33//! podman login docker.io
34//! podman run -d --name jaeger \
35//!  -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
36//!  -p 5775:5775/udp \
37//!  -p 6831:6831/udp \
38//!  -p 6832:6832/udp \
39//!  -p 5778:5778 \
40//!  -p 16686:16686 \
41//!  -p 14268:14268 \
42//!  -p 14250:14250 \
43//!  -p 9411:9411 \
44//!  docker.io/jaegertracing/all-in-one:1.21
45//! ```
46
47#![forbid(unused_imports)]
48
49mod config;
50mod errors;
51mod spans;
52
53pub use self::{
54	config::{JaegerConfig, JaegerConfigBuilder},
55	errors::JaegerError,
56	spans::{hash_to_trace_identifier, PerLeafSpan, Span, Stage},
57};
58
59use self::spans::TraceIdentifier;
60
61use sp_core::traits::SpawnNamed;
62
63use parking_lot::RwLock;
64use std::{
65	result,
66	sync::{Arc, LazyLock},
67};
68
69static INSTANCE: LazyLock<RwLock<Jaeger>> = LazyLock::new(|| RwLock::new(Jaeger::None));
70
71/// Stateful convenience wrapper around [`mick_jaeger`].
72pub enum Jaeger {
73	/// Launched and operational state.
74	Launched {
75		/// [`mick_jaeger`] provided API to record spans to.
76		traces_in: Arc<mick_jaeger::TracesIn>,
77	},
78	/// Preparation state with the necessary config to launch the collector.
79	Prep(JaegerConfig),
80	/// Uninitialized, suggests wrong API usage if encountered.
81	None,
82}
83
84impl Jaeger {
85	/// Spawn the jaeger instance.
86	pub fn new(cfg: JaegerConfig) -> Self {
87		Jaeger::Prep(cfg)
88	}
89
90	/// Spawn the background task in order to send the tracing information out via UDP
91	#[cfg(target_os = "unknown")]
92	pub fn launch<S: SpawnNamed>(self, _spawner: S) -> result::Result<(), JaegerError> {
93		Ok(())
94	}
95
96	/// Provide a no-thrills test setup helper.
97	#[cfg(test)]
98	pub fn test_setup() {
99		let mut instance = INSTANCE.write();
100		match *instance {
101			Self::Launched { .. } => {},
102			_ => {
103				let (traces_in, _traces_out) = mick_jaeger::init(mick_jaeger::Config {
104					service_name: "polkadot-jaeger-test".to_owned(),
105				});
106				*instance = Self::Launched { traces_in };
107			},
108		}
109	}
110
111	/// Spawn the background task in order to send the tracing information out via UDP
112	#[cfg(not(target_os = "unknown"))]
113	pub fn launch<S: SpawnNamed>(self, spawner: S) -> result::Result<(), JaegerError> {
114		let cfg = match self {
115			Self::Prep(cfg) => Ok(cfg),
116			Self::Launched { .. } => return Err(JaegerError::AlreadyLaunched),
117			Self::None => Err(JaegerError::MissingConfiguration),
118		}?;
119
120		let jaeger_agent = cfg.agent_addr;
121
122		log::info!("🐹 Collecting jaeger spans for {:?}", &jaeger_agent);
123
124		let (traces_in, mut traces_out) = mick_jaeger::init(mick_jaeger::Config {
125			service_name: format!("polkadot-{}", cfg.node_name),
126		});
127
128		// Spawn a background task that pulls span information and sends them on the network.
129		spawner.spawn(
130			"jaeger-collector",
131			Some("jaeger"),
132			Box::pin(async move {
133				match tokio::net::UdpSocket::bind("0.0.0.0:0").await {
134					Ok(udp_socket) => loop {
135						let buf = traces_out.next().await;
136						// UDP sending errors happen only either if the API is misused or in case of
137						// missing privilege.
138						if let Err(e) = udp_socket.send_to(&buf, jaeger_agent).await {
139							log::debug!(target: "jaeger", "UDP send error: {}", e);
140						}
141					},
142					Err(e) => {
143						log::warn!(target: "jaeger", "UDP socket open error: {}", e);
144					},
145				}
146			}),
147		);
148
149		*INSTANCE.write() = Self::Launched { traces_in };
150		Ok(())
151	}
152
153	/// Create a span, but defer the evaluation/transformation into a `TraceIdentifier`.
154	///
155	/// The deferral allows to avoid the additional CPU runtime cost in case of
156	/// items that are not a pre-computed hash by themselves.
157	pub(crate) fn span<F>(&self, lazy_hash: F, span_name: &'static str) -> Option<mick_jaeger::Span>
158	where
159		F: Fn() -> TraceIdentifier,
160	{
161		if let Self::Launched { traces_in, .. } = self {
162			let ident = lazy_hash();
163			let trace_id = std::num::NonZeroU128::new(ident)?;
164			Some(traces_in.span(trace_id, span_name))
165		} else {
166			None
167		}
168	}
169}