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}