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::{result, sync::Arc};
65
66lazy_static::lazy_static! {
67	static ref INSTANCE: RwLock<Jaeger> = RwLock::new(Jaeger::None);
68}
69
70/// Stateful convenience wrapper around [`mick_jaeger`].
71pub enum Jaeger {
72	/// Launched and operational state.
73	Launched {
74		/// [`mick_jaeger`] provided API to record spans to.
75		traces_in: Arc<mick_jaeger::TracesIn>,
76	},
77	/// Preparation state with the necessary config to launch the collector.
78	Prep(JaegerConfig),
79	/// Uninitialized, suggests wrong API usage if encountered.
80	None,
81}
82
83impl Jaeger {
84	/// Spawn the jaeger instance.
85	pub fn new(cfg: JaegerConfig) -> Self {
86		Jaeger::Prep(cfg)
87	}
88
89	/// Spawn the background task in order to send the tracing information out via UDP
90	#[cfg(target_os = "unknown")]
91	pub fn launch<S: SpawnNamed>(self, _spawner: S) -> result::Result<(), JaegerError> {
92		Ok(())
93	}
94
95	/// Provide a no-thrills test setup helper.
96	#[cfg(test)]
97	pub fn test_setup() {
98		let mut instance = INSTANCE.write();
99		match *instance {
100			Self::Launched { .. } => {},
101			_ => {
102				let (traces_in, _traces_out) = mick_jaeger::init(mick_jaeger::Config {
103					service_name: "polkadot-jaeger-test".to_owned(),
104				});
105				*instance = Self::Launched { traces_in };
106			},
107		}
108	}
109
110	/// Spawn the background task in order to send the tracing information out via UDP
111	#[cfg(not(target_os = "unknown"))]
112	pub fn launch<S: SpawnNamed>(self, spawner: S) -> result::Result<(), JaegerError> {
113		let cfg = match self {
114			Self::Prep(cfg) => Ok(cfg),
115			Self::Launched { .. } => return Err(JaegerError::AlreadyLaunched),
116			Self::None => Err(JaegerError::MissingConfiguration),
117		}?;
118
119		let jaeger_agent = cfg.agent_addr;
120
121		log::info!("🐹 Collecting jaeger spans for {:?}", &jaeger_agent);
122
123		let (traces_in, mut traces_out) = mick_jaeger::init(mick_jaeger::Config {
124			service_name: format!("polkadot-{}", cfg.node_name),
125		});
126
127		// Spawn a background task that pulls span information and sends them on the network.
128		spawner.spawn(
129			"jaeger-collector",
130			Some("jaeger"),
131			Box::pin(async move {
132				match tokio::net::UdpSocket::bind("0.0.0.0:0").await {
133					Ok(udp_socket) => loop {
134						let buf = traces_out.next().await;
135						// UDP sending errors happen only either if the API is misused or in case of
136						// missing privilege.
137						if let Err(e) = udp_socket.send_to(&buf, jaeger_agent).await {
138							log::debug!(target: "jaeger", "UDP send error: {}", e);
139						}
140					},
141					Err(e) => {
142						log::warn!(target: "jaeger", "UDP socket open error: {}", e);
143					},
144				}
145			}),
146		);
147
148		*INSTANCE.write() = Self::Launched { traces_in };
149		Ok(())
150	}
151
152	/// Create a span, but defer the evaluation/transformation into a `TraceIdentifier`.
153	///
154	/// The deferral allows to avoid the additional CPU runtime cost in case of
155	/// items that are not a pre-computed hash by themselves.
156	pub(crate) fn span<F>(&self, lazy_hash: F, span_name: &'static str) -> Option<mick_jaeger::Span>
157	where
158		F: Fn() -> TraceIdentifier,
159	{
160		if let Self::Launched { traces_in, .. } = self {
161			let ident = lazy_hash();
162			let trace_id = std::num::NonZeroU128::new(ident)?;
163			Some(traces_in.span(trace_id, span_name))
164		} else {
165			None
166		}
167	}
168}