polkadot_node_jaeger/
spans.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 span definitions.
18//!
19//! ```rust
20//! # use polkadot_primitives::{CandidateHash, Hash};
21//! # fn main() {
22//! use polkadot_node_jaeger as jaeger;
23//!
24//! let relay_parent = Hash::default();
25//! let candidate = CandidateHash::default();
26//!
27//! #[derive(Debug, Default)]
28//! struct Foo {
29//! 	a: u8,
30//! 	b: u16,
31//! 	c: u32,
32//! };
33//!
34//! let foo = Foo::default();
35//!
36//! let span =
37//! 	jaeger::Span::new(relay_parent, "root_of_aaall_spans")
38//! 		// explicit well defined items
39//! 		.with_candidate(candidate)
40//! 		// anything that implements `trait std::fmt::Debug`
41//! 		.with_string_fmt_debug_tag("foo", foo)
42//! 		// anything that implements `trait std::str::ToString`
43//! 		.with_string_tag("again", 1337_u32)
44//! 		// add a `Stage` for [`dot-jaeger`](https://github.com/paritytech/dot-jaeger)
45//! 		.with_stage(jaeger::Stage::CandidateBacking);
46//! 		// complete by design, no completion required
47//! # }
48//! ```
49//!
50//! In a few cases additional annotations might want to be added
51//! over the course of a function, for this purpose use the non-consuming
52//! `fn` variants, i.e.
53//! ```rust
54//! # use polkadot_primitives::{CandidateHash, Hash};
55//! # fn main() {
56//! # use polkadot_node_jaeger as jaeger;
57//!
58//! # let relay_parent = Hash::default();
59//! # let candidate = CandidateHash::default();
60//!
61//! # #[derive(Debug, Default)]
62//! # struct Foo {
63//! # 	a: u8,
64//! # 	b: u16,
65//! # 	c: u32,
66//! # };
67//! #
68//! # let foo = Foo::default();
69//!
70//! let root_span =
71//! 	jaeger::Span::new(relay_parent, "root_of_aaall_spans");
72//!
73//! // the preferred way of adding additional delayed information:
74//! let span = root_span.child("inner");
75//!
76//! // ... more operations ...
77//!
78//! // but this is also possible:
79//!
80//! let mut root_span = root_span;
81//! root_span.add_string_fmt_debug_tag("foo_constructed", &foo);
82//! root_span.add_string_tag("bar", true);
83//! # }
84//! ```
85
86use codec::Encode;
87use polkadot_node_primitives::PoV;
88use polkadot_primitives::{
89	BlakeTwo256, CandidateHash, ChunkIndex, Hash, HashT, Id as ParaId, ValidatorIndex,
90};
91use sc_network_types::PeerId;
92
93use std::{fmt, sync::Arc};
94
95use super::INSTANCE;
96
97/// A special "per leaf span".
98///
99/// Essentially this span wraps two spans:
100///
101/// 1. The span that is created per leaf in the overseer.
102/// 2. Some child span of the per-leaf span.
103///
104/// This just works as auxiliary structure to easily store both.
105#[derive(Debug)]
106pub struct PerLeafSpan {
107	leaf_span: Arc<Span>,
108	span: Span,
109}
110
111impl PerLeafSpan {
112	/// Creates a new instance.
113	///
114	/// Takes the `leaf_span` that is created by the overseer per leaf and a name for a child span.
115	/// Both will be stored in this object, while the child span is implicitly accessible by using
116	/// the [`Deref`](std::ops::Deref) implementation.
117	pub fn new(leaf_span: Arc<Span>, name: &'static str) -> Self {
118		let span = leaf_span.child(name);
119
120		Self { span, leaf_span }
121	}
122
123	/// Returns the leaf span.
124	pub fn leaf_span(&self) -> &Arc<Span> {
125		&self.leaf_span
126	}
127}
128
129/// Returns a reference to the child span.
130impl std::ops::Deref for PerLeafSpan {
131	type Target = Span;
132
133	fn deref(&self) -> &Span {
134		&self.span
135	}
136}
137
138/// A helper to annotate the stage with a numerical value
139/// to ease the life of the tooling team creating viable
140/// statistical metrics for which stage of the inclusion
141/// pipeline drops a significant amount of candidates,
142/// statistically speaking.
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144#[repr(u8)]
145#[non_exhaustive]
146pub enum Stage {
147	CandidateBacking = 2,
148	StatementDistribution = 3,
149	PoVDistribution = 4,
150	AvailabilityDistribution = 5,
151	AvailabilityRecovery = 6,
152	BitfieldDistribution = 7,
153	ApprovalChecking = 8,
154	ApprovalDistribution = 9,
155	// Expand as needed, numbers should be ascending according to the stage
156	// through the inclusion pipeline, or according to the descriptions
157	// in [the path of a para chain block]
158	// (https://polkadot.network/the-path-of-a-parachain-block/)
159	// see [issue](https://github.com/paritytech/polkadot/issues/2389)
160}
161
162/// A wrapper type for a span.
163///
164/// Handles running with and without jaeger.
165pub enum Span {
166	/// Running with jaeger being enabled.
167	Enabled(mick_jaeger::Span),
168	/// Running with jaeger disabled.
169	Disabled,
170}
171
172/// Alias for the 16 byte unique identifier used with jaeger.
173pub(crate) type TraceIdentifier = u128;
174
175/// A helper to convert the hash to the fixed size representation
176/// needed for jaeger.
177#[inline]
178pub fn hash_to_trace_identifier(hash: Hash) -> TraceIdentifier {
179	let mut buf = [0u8; 16];
180	buf.copy_from_slice(&hash.as_ref()[0..16]);
181	// The slice bytes are copied in reading order, so if interpreted
182	// in string form by a human, that means lower indices have higher
183	// values and hence corresponds to BIG endian ordering of the individual
184	// bytes.
185	u128::from_be_bytes(buf) as TraceIdentifier
186}
187
188/// Helper to unify lazy proxy evaluation.
189pub trait LazyIdent {
190	/// Evaluate the type to a unique trace identifier.
191	/// Called lazily on demand.
192	fn eval(&self) -> TraceIdentifier;
193
194	/// Annotate a new root item with these additional spans
195	/// at construction.
196	fn extra_tags(&self, _span: &mut Span) {}
197}
198
199impl<'a> LazyIdent for &'a [u8] {
200	fn eval(&self) -> TraceIdentifier {
201		hash_to_trace_identifier(BlakeTwo256::hash_of(self))
202	}
203}
204
205impl LazyIdent for &PoV {
206	fn eval(&self) -> TraceIdentifier {
207		hash_to_trace_identifier(self.hash())
208	}
209
210	fn extra_tags(&self, span: &mut Span) {
211		span.add_pov(self)
212	}
213}
214
215impl LazyIdent for Hash {
216	fn eval(&self) -> TraceIdentifier {
217		hash_to_trace_identifier(*self)
218	}
219
220	fn extra_tags(&self, span: &mut Span) {
221		span.add_string_fmt_debug_tag("relay-parent", self);
222	}
223}
224
225impl LazyIdent for &Hash {
226	fn eval(&self) -> TraceIdentifier {
227		hash_to_trace_identifier(**self)
228	}
229
230	fn extra_tags(&self, span: &mut Span) {
231		span.add_string_fmt_debug_tag("relay-parent", self);
232	}
233}
234
235impl LazyIdent for CandidateHash {
236	fn eval(&self) -> TraceIdentifier {
237		hash_to_trace_identifier(self.0)
238	}
239
240	fn extra_tags(&self, span: &mut Span) {
241		span.add_string_fmt_debug_tag("candidate-hash", &self.0);
242		// A convenience for usage with the grafana tempo UI,
243		// not a technical requirement. It merely provides an easy anchor
244		// where the true trace identifier of the span is not based on
245		// a candidate hash (which it should be!), but is required to
246		// continue investigating.
247		span.add_string_tag("traceID", self.eval().to_string());
248	}
249}
250
251impl Span {
252	/// Creates a new span builder based on anything that can be lazily evaluated
253	/// to and identifier.
254	///
255	/// Attention: The primary identifier will be used for identification
256	/// and as such should be
257	pub fn new<I: LazyIdent>(identifier: I, span_name: &'static str) -> Span {
258		let mut span = INSTANCE
259			.read_recursive()
260			.span(|| <I as LazyIdent>::eval(&identifier), span_name)
261			.into();
262		<I as LazyIdent>::extra_tags(&identifier, &mut span);
263		span
264	}
265
266	/// Creates a new span builder based on an encodable type.
267	/// The encoded bytes are then used to derive the true trace identifier.
268	pub fn from_encodable<I: Encode>(identifier: I, span_name: &'static str) -> Span {
269		INSTANCE
270			.read_recursive()
271			.span(
272				move || {
273					let bytes = identifier.encode();
274					LazyIdent::eval(&bytes.as_slice())
275				},
276				span_name,
277			)
278			.into()
279	}
280
281	/// Derive a child span from `self`.
282	pub fn child(&self, name: &str) -> Self {
283		match self {
284			Self::Enabled(inner) => Self::Enabled(inner.child(name)),
285			Self::Disabled => Self::Disabled,
286		}
287	}
288
289	/// Attach a 'traceID' tag set to the decimal representation of the candidate hash.
290	#[inline(always)]
291	pub fn with_trace_id(mut self, candidate_hash: CandidateHash) -> Self {
292		self.add_string_tag("traceID", hash_to_trace_identifier(candidate_hash.0));
293		self
294	}
295
296	#[inline(always)]
297	pub fn with_string_tag<V: ToString>(mut self, tag: &'static str, val: V) -> Self {
298		self.add_string_tag::<V>(tag, val);
299		self
300	}
301
302	/// Attach a peer-id tag to the span.
303	#[inline(always)]
304	pub fn with_peer_id(self, peer: &PeerId) -> Self {
305		self.with_string_tag("peer-id", &peer.to_base58())
306	}
307
308	/// Attach a `peer-id` tag to the span when peer is present.
309	#[inline(always)]
310	pub fn with_optional_peer_id(self, peer: Option<&PeerId>) -> Self {
311		if let Some(peer) = peer {
312			self.with_peer_id(peer)
313		} else {
314			self
315		}
316	}
317
318	/// Attach a candidate hash to the span.
319	#[inline(always)]
320	pub fn with_candidate(self, candidate_hash: CandidateHash) -> Self {
321		self.with_string_fmt_debug_tag("candidate-hash", &candidate_hash.0)
322	}
323
324	/// Attach a para-id to the span.
325	#[inline(always)]
326	pub fn with_para_id(self, para_id: ParaId) -> Self {
327		self.with_int_tag("para-id", u32::from(para_id) as i64)
328	}
329
330	/// Attach a candidate stage.
331	/// Should always come with a `CandidateHash`.
332	#[inline(always)]
333	pub fn with_stage(self, stage: Stage) -> Self {
334		self.with_string_tag("candidate-stage", stage as u8)
335	}
336
337	#[inline(always)]
338	pub fn with_validator_index(self, validator: ValidatorIndex) -> Self {
339		self.with_string_tag("validator-index", &validator.0)
340	}
341
342	#[inline(always)]
343	pub fn with_chunk_index(self, chunk_index: ChunkIndex) -> Self {
344		self.with_string_tag("chunk-index", &chunk_index.0)
345	}
346
347	#[inline(always)]
348	pub fn with_relay_parent(self, relay_parent: Hash) -> Self {
349		self.with_string_fmt_debug_tag("relay-parent", relay_parent)
350	}
351
352	#[inline(always)]
353	pub fn with_claimed_validator_index(self, claimed_validator_index: ValidatorIndex) -> Self {
354		self.with_string_tag("claimed-validator", &claimed_validator_index.0)
355	}
356
357	#[inline(always)]
358	pub fn with_pov(mut self, pov: &PoV) -> Self {
359		self.add_pov(pov);
360		self
361	}
362
363	/// Add an additional int tag to the span without consuming.
364	///
365	/// Should be used sparingly, introduction of new types is preferred.
366	#[inline(always)]
367	pub fn with_int_tag(mut self, tag: &'static str, i: i64) -> Self {
368		self.add_int_tag(tag, i);
369		self
370	}
371
372	#[inline(always)]
373	pub fn with_uint_tag(mut self, tag: &'static str, u: u64) -> Self {
374		self.add_uint_tag(tag, u);
375		self
376	}
377
378	#[inline(always)]
379	pub fn with_string_fmt_debug_tag<V: fmt::Debug>(mut self, tag: &'static str, val: V) -> Self {
380		self.add_string_tag(tag, format!("{:?}", val));
381		self
382	}
383
384	/// Adds the `FollowsFrom` relationship to this span with respect to the given one.
385	#[inline(always)]
386	pub fn add_follows_from(&mut self, other: &Self) {
387		match (self, other) {
388			(Self::Enabled(ref mut inner), Self::Enabled(ref other_inner)) =>
389				inner.add_follows_from(&other_inner),
390			_ => {},
391		}
392	}
393
394	/// Add a PoV hash meta tag with lazy hash evaluation, without consuming the span.
395	#[inline(always)]
396	pub fn add_pov(&mut self, pov: &PoV) {
397		if self.is_enabled() {
398			// avoid computing the PoV hash if jaeger is not enabled
399			self.add_string_fmt_debug_tag("pov", pov.hash());
400		}
401	}
402
403	#[inline(always)]
404	pub fn add_para_id(&mut self, para_id: ParaId) {
405		self.add_int_tag("para-id", u32::from(para_id) as i64);
406	}
407
408	/// Add a string tag, without consuming the span.
409	pub fn add_string_tag<V: ToString>(&mut self, tag: &'static str, val: V) {
410		match self {
411			Self::Enabled(ref mut inner) => inner.add_string_tag(tag, val.to_string().as_str()),
412			Self::Disabled => {},
413		}
414	}
415
416	/// Add a string tag, without consuming the span.
417	pub fn add_string_fmt_debug_tag<V: fmt::Debug>(&mut self, tag: &'static str, val: V) {
418		match self {
419			Self::Enabled(ref mut inner) =>
420				inner.add_string_tag(tag, format!("{:?}", val).as_str()),
421			Self::Disabled => {},
422		}
423	}
424
425	pub fn add_int_tag(&mut self, tag: &'static str, value: i64) {
426		match self {
427			Self::Enabled(ref mut inner) => inner.add_int_tag(tag, value),
428			Self::Disabled => {},
429		}
430	}
431
432	pub fn add_uint_tag(&mut self, tag: &'static str, value: u64) {
433		match self {
434			Self::Enabled(ref mut inner) => inner.add_int_tag(tag, value as i64),
435			Self::Disabled => {},
436		}
437	}
438
439	/// Check whether jaeger is enabled
440	/// in order to avoid computational overhead.
441	pub const fn is_enabled(&self) -> bool {
442		match self {
443			Span::Enabled(_) => true,
444			_ => false,
445		}
446	}
447
448	/// Obtain the trace identifier for this set of spans.
449	pub fn trace_id(&self) -> Option<TraceIdentifier> {
450		match self {
451			Span::Enabled(inner) => Some(inner.trace_id().get()),
452			_ => None,
453		}
454	}
455}
456
457impl std::fmt::Debug for Span {
458	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
459		write!(f, "<jaeger span>")
460	}
461}
462
463impl From<Option<mick_jaeger::Span>> for Span {
464	fn from(src: Option<mick_jaeger::Span>) -> Self {
465		if let Some(span) = src {
466			Self::Enabled(span)
467		} else {
468			Self::Disabled
469		}
470	}
471}
472
473impl From<mick_jaeger::Span> for Span {
474	fn from(src: mick_jaeger::Span) -> Self {
475		Self::Enabled(src)
476	}
477}
478
479#[cfg(test)]
480mod tests {
481	use super::*;
482	use crate::Jaeger;
483
484	// make sure to not use `::repeat_*()` based samples, since this does not verify endianness
485	const RAW: [u8; 32] = [
486		0xFF, 0xAA, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x78, 0x89, 0x9A, 0xAB, 0xBC, 0xCD, 0xDE,
487		0xEF, 0x00, 0x01, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
488		0x0E, 0x0F,
489	];
490
491	#[test]
492	fn hash_derived_identifier_is_leading_16bytes() {
493		let candidate_hash = dbg!(Hash::from(&RAW));
494		let trace_id = dbg!(hash_to_trace_identifier(candidate_hash));
495		for (idx, (a, b)) in candidate_hash
496			.as_bytes()
497			.iter()
498			.take(16)
499			.zip(trace_id.to_be_bytes().iter())
500			.enumerate()
501		{
502			assert_eq!(*a, *b, "Index [{}] does not match: {} != {}", idx, a, b);
503		}
504	}
505
506	#[test]
507	fn extra_tags_do_not_change_trace_id() {
508		Jaeger::test_setup();
509		let candidate_hash = dbg!(Hash::from(&RAW));
510		let trace_id = hash_to_trace_identifier(candidate_hash);
511
512		let span = Span::new(candidate_hash, "foo");
513
514		assert_eq!(span.trace_id(), Some(trace_id));
515
516		let span = span.with_int_tag("tag", 7i64);
517
518		assert_eq!(span.trace_id(), Some(trace_id));
519	}
520}