1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.

// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.

//! Polkadot Jaeger related primitives
//!
//! Provides primitives used by Polkadot for interfacing with Jaeger.
//!
//! # Integration
//!
//! See <https://www.jaegertracing.io/> for an introduction.
//!
//! The easiest way to try Jaeger is:
//!
//! - Start a docker container with the all-in-one docker image (see below).
//! - Open your browser and navigate to <https://localhost:16686> to access the UI.
//!
//! The all-in-one image can be started with:
//!
//! ```not_rust
//! podman login docker.io
//! podman run -d --name jaeger \
//!  -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
//!  -p 5775:5775/udp \
//!  -p 6831:6831/udp \
//!  -p 6832:6832/udp \
//!  -p 5778:5778 \
//!  -p 16686:16686 \
//!  -p 14268:14268 \
//!  -p 14250:14250 \
//!  -p 9411:9411 \
//!  docker.io/jaegertracing/all-in-one:1.21
//! ```

#![forbid(unused_imports)]

mod config;
mod errors;
mod spans;

pub use self::{
	config::{JaegerConfig, JaegerConfigBuilder},
	errors::JaegerError,
	spans::{hash_to_trace_identifier, PerLeafSpan, Span, Stage},
};

use self::spans::TraceIdentifier;

use sp_core::traits::SpawnNamed;

use parking_lot::RwLock;
use std::{result, sync::Arc};

lazy_static::lazy_static! {
	static ref INSTANCE: RwLock<Jaeger> = RwLock::new(Jaeger::None);
}

/// Stateful convenience wrapper around [`mick_jaeger`].
pub enum Jaeger {
	/// Launched and operational state.
	Launched {
		/// [`mick_jaeger`] provided API to record spans to.
		traces_in: Arc<mick_jaeger::TracesIn>,
	},
	/// Preparation state with the necessary config to launch the collector.
	Prep(JaegerConfig),
	/// Uninitialized, suggests wrong API usage if encountered.
	None,
}

impl Jaeger {
	/// Spawn the jaeger instance.
	pub fn new(cfg: JaegerConfig) -> Self {
		Jaeger::Prep(cfg)
	}

	/// Spawn the background task in order to send the tracing information out via UDP
	#[cfg(target_os = "unknown")]
	pub fn launch<S: SpawnNamed>(self, _spawner: S) -> result::Result<(), JaegerError> {
		Ok(())
	}

	/// Provide a no-thrills test setup helper.
	#[cfg(test)]
	pub fn test_setup() {
		let mut instance = INSTANCE.write();
		match *instance {
			Self::Launched { .. } => {},
			_ => {
				let (traces_in, _traces_out) = mick_jaeger::init(mick_jaeger::Config {
					service_name: "polkadot-jaeger-test".to_owned(),
				});
				*instance = Self::Launched { traces_in };
			},
		}
	}

	/// Spawn the background task in order to send the tracing information out via UDP
	#[cfg(not(target_os = "unknown"))]
	pub fn launch<S: SpawnNamed>(self, spawner: S) -> result::Result<(), JaegerError> {
		let cfg = match self {
			Self::Prep(cfg) => Ok(cfg),
			Self::Launched { .. } => return Err(JaegerError::AlreadyLaunched),
			Self::None => Err(JaegerError::MissingConfiguration),
		}?;

		let jaeger_agent = cfg.agent_addr;

		log::info!("🐹 Collecting jaeger spans for {:?}", &jaeger_agent);

		let (traces_in, mut traces_out) = mick_jaeger::init(mick_jaeger::Config {
			service_name: format!("polkadot-{}", cfg.node_name),
		});

		// Spawn a background task that pulls span information and sends them on the network.
		spawner.spawn(
			"jaeger-collector",
			Some("jaeger"),
			Box::pin(async move {
				match tokio::net::UdpSocket::bind("0.0.0.0:0").await {
					Ok(udp_socket) => loop {
						let buf = traces_out.next().await;
						// UDP sending errors happen only either if the API is misused or in case of
						// missing privilege.
						if let Err(e) = udp_socket.send_to(&buf, jaeger_agent).await {
							log::debug!(target: "jaeger", "UDP send error: {}", e);
						}
					},
					Err(e) => {
						log::warn!(target: "jaeger", "UDP socket open error: {}", e);
					},
				}
			}),
		);

		*INSTANCE.write() = Self::Launched { traces_in };
		Ok(())
	}

	/// Create a span, but defer the evaluation/transformation into a `TraceIdentifier`.
	///
	/// The deferral allows to avoid the additional CPU runtime cost in case of
	/// items that are not a pre-computed hash by themselves.
	pub(crate) fn span<F>(&self, lazy_hash: F, span_name: &'static str) -> Option<mick_jaeger::Span>
	where
		F: Fn() -> TraceIdentifier,
	{
		if let Self::Launched { traces_in, .. } = self {
			let ident = lazy_hash();
			let trace_id = std::num::NonZeroU128::new(ident)?;
			Some(traces_in.span(trace_id, span_name))
		} else {
			None
		}
	}
}