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}