use std::{
sync::Arc,
time::{Duration, Instant},
};
use blake2b_simd::Params as Blake2bParams;
use serde_json::{Map, Number, Value};
use zakura_jsonl_trace::{JsonlTracer, JsonlWriteEvent};
use super::{ZakuraPeerId, ZakuraRejectReason};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct ZakuraTraceTable {
table: &'static str,
file_name: &'static str,
}
impl ZakuraTraceTable {
pub fn table(self) -> &'static str {
self.table
}
pub fn file_name(self) -> &'static str {
self.file_name
}
}
pub const HANDSHAKE_TABLE: ZakuraTraceTable = ZakuraTraceTable {
table: "handshake",
file_name: "handshake.jsonl",
};
pub const CONN_TABLE: ZakuraTraceTable = ZakuraTraceTable {
table: "conn",
file_name: "conn.jsonl",
};
pub const STREAM_TABLE: ZakuraTraceTable = ZakuraTraceTable {
table: "stream",
file_name: "stream.jsonl",
};
pub const DISCOVERY_TABLE: ZakuraTraceTable = ZakuraTraceTable {
table: "discovery",
file_name: "discovery.jsonl",
};
pub const RATELIMIT_TABLE: ZakuraTraceTable = ZakuraTraceTable {
table: "ratelimit",
file_name: "ratelimit.jsonl",
};
pub const HEADER_SYNC_TABLE: ZakuraTraceTable = ZakuraTraceTable {
table: "header_sync",
file_name: "header_sync.jsonl",
};
pub const LEGACY_REQUEST_TABLE: ZakuraTraceTable = ZakuraTraceTable {
table: "legacy_request",
file_name: "legacy_request.jsonl",
};
pub const BLOCK_SYNC_TABLE: ZakuraTraceTable = ZakuraTraceTable {
table: "block_sync",
file_name: "block_sync.jsonl",
};
pub const COMMIT_STATE_TABLE: ZakuraTraceTable = ZakuraTraceTable {
table: "commit_state",
file_name: "commit_state.jsonl",
};
pub const QUEUE_SEND_TABLE: ZakuraTraceTable = ZakuraTraceTable {
table: "queue_send",
file_name: "queue_send.jsonl",
};
pub mod queue_send_trace {
pub const EVENT: &str = "event";
pub const QUEUE_SEND_FAILED: &str = "queue_send_failed";
pub const SERVICE: &str = "service";
pub const MESSAGE: &str = "message";
pub const PEER: &str = "peer";
pub const SOURCE_PEER: &str = "source_peer";
pub const DESTINATION_PEER: &str = "destination_peer";
pub const ERROR: &str = "error";
pub const REASON: &str = "reason";
pub const QUEUE_CAPACITY: &str = "queue_capacity";
pub const QUEUE_MAX_CAPACITY: &str = "queue_max_capacity";
pub const RANGE_START: &str = "range_start";
pub const RANGE_COUNT: &str = "range_count";
pub const RETURNED: &str = "returned";
pub const HEIGHT: &str = "height";
pub const HASH: &str = "hash";
}
pub mod block_sync_trace {
pub const EVENT: &str = "event";
pub const PEER: &str = "peer";
pub const KIND: &str = "kind";
pub const HEIGHT: &str = "height";
pub const HASH: &str = "hash";
pub const RANGE_START: &str = "range_start";
pub const RANGE_COUNT: &str = "range_count";
pub const EXPECTED_COUNT: &str = "expected_count";
pub const ESTIMATED_BYTES: &str = "estimated_bytes";
pub const SERIALIZED_BYTES: &str = "serialized_bytes";
pub const DECODED_ATTRIBUTED_MEMORY_SIZE_BYTES: &str = "decoded_attributed_memory_size_bytes";
pub const SEQUENCER_INPUT_DECODED_ATTRIBUTED_MEMORY_BYTES: &str =
"sequencer_input_decoded_attributed_memory_bytes";
pub const REORDER_DECODED_ATTRIBUTED_MEMORY_BYTES: &str =
"reorder_decoded_attributed_memory_bytes";
pub const APPLYING_DECODED_ATTRIBUTED_MEMORY_BYTES: &str =
"applying_decoded_attributed_memory_bytes";
pub const ACTIVE_PIPELINE_DECODED_ATTRIBUTED_MEMORY_BYTES: &str =
"active_pipeline_decoded_attributed_memory_bytes";
pub const ELAPSED_MS: &str = "elapsed_ms";
pub const PREPARE_ELAPSED_MS: &str = "prepare_elapsed_ms";
pub const SEND_ELAPSED_MS: &str = "send_elapsed_ms";
pub const RESULT: &str = "result";
pub const REASON: &str = "reason";
pub const ERROR: &str = "error";
pub const APPLY_TOKEN: &str = "apply_token";
pub const REQUEST_FLOOR: &str = "request_floor";
pub const BODY_DOWNLOAD_FLOOR: &str = "body_download_floor";
pub const FLOOR_GAP_HEIGHT: &str = "floor_gap_height";
pub const FLOOR_GAP_STATE: &str = "floor_gap_state";
pub const FLOOR_GAP_SERVABLE_PEERS: &str = "floor_gap_servable_peers";
pub const FLOOR_GAP_AVAILABLE_PEERS: &str = "floor_gap_available_peers";
pub const FLOOR_GAP_OUTSTANDING_PEERS: &str = "floor_gap_outstanding_peers";
pub const FLOOR_GAP_OLDEST_OUTSTANDING_MS: &str = "floor_gap_oldest_outstanding_ms";
pub const FLOOR_GAP_NEXT_DEADLINE_MS: &str = "floor_gap_next_deadline_ms";
pub const VERIFIED_BLOCK_TIP: &str = "verified_block_tip";
pub const BEST_HEADER_TIP: &str = "best_header_tip";
pub const BODY_LAG: &str = "body_lag";
pub const APPLYING: &str = "applying";
pub const SUBMITTED_APPLIES: &str = "submitted_applies";
pub const REORDER: &str = "reorder";
pub const OUTSTANDING: &str = "outstanding";
pub const BUDGET_AVAILABLE: &str = "budget_available";
pub const BUDGET_RESERVED: &str = "budget_reserved";
pub const BUDGET_RESERVED_AFTER: &str = "budget_reserved_after";
pub const RECEIVED_BYTES_PER_SEC: &str = "received_bytes_per_sec";
pub const RECEIVED_BLOCKS_PER_SEC: &str = "received_blocks_per_sec";
pub const COMMITTED_BYTES_PER_SEC: &str = "committed_bytes_per_sec";
pub const COMMITTED_BLOCKS_PER_SEC: &str = "committed_blocks_per_sec";
pub const DOWNLOAD_BLOCKED_ON_BUDGET: &str = "download_blocked_on_budget";
pub const PEERS_WANTING_SLOTS: &str = "peers_wanting_slots";
pub const PEERS: &str = "peers";
pub const ACTIVE_CONNECTIONS: &str = "active_connections";
pub const PEERS_WITH_STATUS: &str = "peers_with_status";
pub const NEEDED_MIN: &str = "needed_min";
pub const NEEDED_COUNT: &str = "needed_count";
pub const QUEUE_LEN: &str = "queue_len";
pub const QUEUE_BLOCKS: &str = "queue_blocks";
pub const QUEUE_MIN_START: &str = "queue_min_start";
pub const ASSIGNED_LEN: &str = "assigned_len";
pub const LOCAL_BODY_WORK: &str = "local_body_work";
pub const REFILL_LOW_WATER: &str = "refill_low_water";
pub const COVERED_MAX_END: &str = "covered_max_end";
pub const BLOCK_STATUS_RECEIVED: &str = "block_status_received";
pub const BLOCK_STATUS_SENT: &str = "block_status_sent";
pub const BLOCK_STATUS_SEND_FAILED: &str = "block_status_send_failed";
pub const BLOCK_PEER_CONNECTED: &str = "block_peer_connected";
pub const BLOCK_PEER_DISCONNECTED: &str = "block_peer_disconnected";
pub const BLOCK_PEER_PROTOCOL_REJECT: &str = "block_peer_protocol_reject";
pub const BLOCK_GET_BLOCKS_SENT: &str = "block_get_blocks_sent";
pub const BLOCK_EVENT_RECEIVED: &str = "block_event_received";
pub const BLOCK_MESSAGE_RECEIVED: &str = "block_message_received";
pub const BLOCK_MESSAGE_SENT: &str = "block_message_sent";
pub const BLOCK_ACTION_DISPATCHED: &str = "block_action_dispatched";
pub const BLOCK_BODY_RECEIVED: &str = "block_body_received";
pub const BLOCK_BODY_SEQUENCER_SENT: &str = "block_body_sequencer_sent";
pub const BLOCK_BODY_DECODE_PERMIT: &str = "block_body_decode_permit";
pub const BLOCK_BODY_ACCEPTED: &str = "block_body_accepted";
pub const BLOCK_SEQUENCER_CONTROL_SENT: &str = "block_sequencer_control_sent";
pub const BLOCK_BODY_SUBMITTED: &str = "block_body_submitted";
pub const BLOCK_BODY_SUBMISSION_RETRY_SCHEDULED: &str = "block_body_submission_retry_scheduled";
pub const BLOCK_APPLY_FINISHED: &str = "block_apply_finished";
pub const BLOCK_RANGE_UNAVAILABLE: &str = "block_range_unavailable";
pub const BLOCK_RANGE_RESPONSE_SENT: &str = "block_range_response_sent";
pub const BLOCK_WORK_EXTENDED: &str = "block_work_extended";
pub const BLOCK_WORK_TAKEN: &str = "block_work_taken";
pub const BLOCK_WORK_RETURNED: &str = "block_work_returned";
pub const BLOCK_FLOOR_WATCHDOG_CANCELLED: &str = "block_floor_watchdog_cancelled";
pub const BLOCK_FILL_STOP: &str = "block_fill_stop";
pub const FILL_STOP_REASON: &str = "fill_stop_reason";
pub const FILL_SENT: &str = "fill_sent";
pub const BLOCK_FRONTIERS_CHANGED: &str = "block_frontiers_changed";
pub const BLOCK_CHAIN_TIP_RESET: &str = "block_chain_tip_reset";
pub const BLOCK_FRONTIER_RESET_CLASSIFIED: &str = "block_frontier_reset_classified";
pub const BLOCK_SYNC_STATE: &str = "block_sync_state";
pub const BLOCK_PEER_BBR: &str = "block_peer_bbr";
}
pub mod discovery_trace {
pub const EVENT: &str = "event";
pub const PEER: &str = "peer";
pub const RESULT: &str = "result";
pub const DISCOVERY_DIAL_RESULT: &str = "discovery_dial_result";
}
pub mod header_sync_trace {
pub const EVENT: &str = "event";
pub const PEER: &str = "peer";
pub const KIND: &str = "kind";
pub const SOURCE_PEER: &str = "source_peer";
pub const HEIGHT: &str = "height";
pub const HASH: &str = "hash";
pub const ANCHOR_HASH: &str = "anchor_hash";
pub const RANGE_START: &str = "range_start";
pub const RANGE_COUNT: &str = "range_count";
pub const VALIDATION_STAGE: &str = "validation_stage";
pub const ERROR_KIND: &str = "error_kind";
pub const ADVERTISED_CAP: &str = "advertised_cap";
pub const EXPECTED_COUNT: &str = "expected_count";
pub const IN_FLIGHT_COUNT: &str = "in_flight_count";
pub const WANT_TREE_AUX_ROOTS: &str = "want_tree_aux_roots";
pub const FINALIZED: &str = "finalized";
pub const RANGE_PRIORITY: &str = "range_priority";
pub const VERIFIED_BLOCK_TIP: &str = "verified_block_tip";
pub const FINALIZED_HEIGHT: &str = "finalized_height";
pub const BEST_HEADER_TIP: &str = "best_header_tip";
pub const TREE_AUX_ROOTS_LEN: &str = "tree_aux_roots_len";
pub const FIRST_ROOT_HEIGHT: &str = "first_root_height";
pub const LAST_ROOT_HEIGHT: &str = "last_root_height";
pub const ROOT_MISMATCH_OFFSET: &str = "root_mismatch_offset";
pub const EXPECTED_ROOT_HEIGHT: &str = "expected_root_height";
pub const ACTUAL_ROOT_HEIGHT: &str = "actual_root_height";
pub const DESTINATION_PEER_COUNT: &str = "destination_peer_count";
pub const ACTIVE_CONNECTIONS: &str = "active_connections";
pub const SESSION_ID: &str = "session_id";
pub const REQUEST_ID: &str = "request_id";
pub const STREAM_VERSION: &str = "stream_version";
pub const REASON: &str = "reason";
pub const HEADER_EVENT_RECEIVED: &str = "header_event_received";
pub const HEADER_ACTION_DISPATCHED: &str = "header_action_dispatched";
pub const HEADER_STATUS_SENT: &str = "header_status_sent";
pub const HEADER_STATUS_RECEIVED: &str = "header_status_received";
pub const HEADER_PEER_CONNECTED: &str = "header_peer_connected";
pub const HEADER_PEER_DISCONNECTED: &str = "header_peer_disconnected";
pub const HEADER_GET_HEADERS_SENT: &str = "header_get_headers_sent";
pub const HEADER_HEADERS_RECEIVED: &str = "header_headers_received";
pub const HEADER_HEADERS_SERVED: &str = "header_headers_served";
pub const HEADER_RANGE_COMMITTED: &str = "header_range_committed";
pub const HEADER_RANGE_REJECTED: &str = "header_range_rejected";
pub const HEADER_NEW_BLOCK_RECEIVED: &str = "header_new_block_received";
pub const HEADER_NEW_BLOCK_FORWARDED: &str = "header_new_block_forwarded";
pub const HEADER_NEW_BLOCK_DEDUPED: &str = "header_new_block_deduped";
pub const HEADER_PEER_VIOLATION: &str = "header_peer_violation";
pub const HEADER_PEER_VIOLATION_RECORDED: &str = "header_peer_violation_recorded";
pub const HEADER_FRONTIER_ADVANCED: &str = "header_frontier_advanced";
pub const HEADER_FRONTIER_REANCHORED: &str = "header_frontier_reanchored";
pub const HEADER_MISSING_BODIES_REPORTED: &str = "header_missing_bodies_reported";
pub const HEADER_MAINTENANCE_WAKEUP: &str = "header_maintenance_wakeup";
}
pub mod commit_state_trace {
pub const EVENT: &str = "event";
pub const SOURCE: &str = "source";
pub const HEIGHT: &str = "height";
pub const HASH: &str = "hash";
pub const RANGE_START: &str = "range_start";
pub const RANGE_COUNT: &str = "range_count";
pub const TREE_AUX_ROOTS_LEN: &str = "tree_aux_roots_len";
pub const RESULT: &str = "result";
pub const ERROR_VARIANT: &str = "error_variant";
pub const ERROR_DEBUG: &str = "error_debug";
pub const REASON: &str = "reason";
pub const APPLY_TOKEN: &str = "apply_token";
pub const APPLY_CLASS: &str = "apply_class";
pub const FINALIZED_HEIGHT: &str = "finalized_height";
pub const VERIFIED_BLOCK_TIP: &str = "verified_block_tip";
pub const VERIFIED_BLOCK_HASH: &str = "verified_block_hash";
pub const BEST_HEADER_TIP: &str = "best_header_tip";
pub const ELAPSED_MS: &str = "elapsed_ms";
pub const COMMIT_STALL_REASON: &str = "commit_stall_reason";
pub const COMMITTED_MARKER: &str = "committed_marker";
pub const FIRED_HIGH_WATER: &str = "fired_high_water";
pub const COMMITS_IN_FLIGHT: &str = "commits_in_flight";
pub const PEER: &str = "peer";
pub const QUEUE_LEN: &str = "queue_len";
pub const IN_FLIGHT_COUNT: &str = "in_flight_count";
pub const ACTION: &str = "action";
pub const LOCAL_FRONTIER: &str = "local_frontier";
pub const ACTION_RECEIVED: &str = "action_received";
pub const STATE_READ_START: &str = "state_read_start";
pub const STATE_READ_SUCCESS: &str = "state_read_success";
pub const STATE_READ_ERROR: &str = "state_read_error";
pub const STATE_READ_TIMEOUT: &str = "state_read_timeout";
pub const BLOCK_SUBMIT_QUEUED: &str = "block_submit_queued";
pub const COMMIT_START: &str = "commit_start";
pub const COMMIT_STALLED: &str = "commit_stalled";
pub const COMMIT_STALL_CONTIGUOUS_HEAD: &str = "contiguous_head";
pub const COMMIT_STALL_BEHIND_PREFIX: &str = "behind_committed_prefix";
pub const COMMIT_FINISH: &str = "commit_finish";
pub const FRONTIER_QUERY_START: &str = "frontier_query_start";
pub const FRONTIER_QUERY_FINISH: &str = "frontier_query_finish";
pub const REACTOR_EVENT_SENT: &str = "reactor_event_sent";
pub const CHECKPOINT_REFRESH_ATTEMPT: &str = "checkpoint_refresh_attempt";
pub const CHECKPOINT_REFRESH_SENT: &str = "checkpoint_refresh_sent";
pub const BLOCK_SYNC_NOTIFY_SENT: &str = "block_sync_notify_sent";
pub const CHAIN_TIP_ACTION: &str = "chain_tip_action";
pub const FRONTIER_DERIVED: &str = "frontier_derived";
pub const SYNC_FRONTIER_TRANSITION: &str = "sync_frontier_transition";
pub const SEQUENCE: &str = "sequence";
pub const CAUSE: &str = "cause";
pub const OLD_FINALIZED_HEIGHT: &str = "old_finalized_height";
pub const OLD_FINALIZED_HASH: &str = "old_finalized_hash";
pub const OLD_VERIFIED_BODY_HEIGHT: &str = "old_verified_body_height";
pub const OLD_VERIFIED_BODY_HASH: &str = "old_verified_body_hash";
pub const OLD_BEST_HEADER_HEIGHT: &str = "old_best_header_height";
pub const OLD_BEST_HEADER_HASH: &str = "old_best_header_hash";
pub const NEW_FINALIZED_HEIGHT: &str = "new_finalized_height";
pub const NEW_FINALIZED_HASH: &str = "new_finalized_hash";
pub const NEW_VERIFIED_BODY_HEIGHT: &str = "new_verified_body_height";
pub const NEW_VERIFIED_BODY_HASH: &str = "new_verified_body_hash";
pub const NEW_BEST_HEADER_HEIGHT: &str = "new_best_header_height";
pub const NEW_BEST_HEADER_HASH: &str = "new_best_header_hash";
}
#[derive(Clone, Debug)]
pub struct ZakuraTrace {
tracer: JsonlTracer,
node: Arc<str>,
started: Instant,
}
impl ZakuraTrace {
pub fn noop() -> Self {
Self::new(JsonlTracer::noop(), zakura_jsonl_trace::node_id())
}
pub fn new(tracer: JsonlTracer, node: impl Into<Arc<str>>) -> Self {
Self {
tracer,
node: node.into(),
started: Instant::now(),
}
}
pub fn tracer(&self) -> &JsonlTracer {
&self.tracer
}
pub fn is_enabled(&self) -> bool {
self.tracer.is_enabled()
}
pub fn emit(&self, table: ZakuraTraceTable, event: ZakuraTraceEvent<'_>) {
self.emit_with(table, |row| event.insert_into(row));
}
pub fn emit_with(&self, table: ZakuraTraceTable, build: impl FnOnce(&mut Map<String, Value>)) {
let Ok(permit) = self.tracer.try_reserve() else {
return;
};
let mut row = Map::new();
row.insert("ts".to_string(), elapsed_micros(self.started.elapsed()));
row.insert("node".to_string(), Value::String(self.node.to_string()));
build(&mut row);
if let Ok(line) = serde_json::to_vec(&Value::Object(row)) {
permit.send(JsonlWriteEvent {
table: table.table(),
file_name: table.file_name(),
line,
});
}
}
}
impl Default for ZakuraTrace {
fn default() -> Self {
Self::noop()
}
}
#[derive(Clone, Debug)]
pub struct ZakuraTraceEvent<'a> {
event: &'static str,
conn: Option<u64>,
stream: Option<u64>,
payload_len: Option<u64>,
frame_len: Option<u64>,
max_frame_bytes: Option<u64>,
peer: Option<&'a str>,
role: Option<&'static str>,
phase: Option<&'static str>,
reason: Option<&'static str>,
selected_protocol: Option<u16>,
direction: Option<&'static str>,
stream_kind: Option<&'static str>,
network: Option<&'static str>,
}
impl<'a> ZakuraTraceEvent<'a> {
pub fn new(event: &'static str) -> Self {
Self {
event,
conn: None,
stream: None,
payload_len: None,
frame_len: None,
max_frame_bytes: None,
peer: None,
role: None,
phase: None,
reason: None,
selected_protocol: None,
direction: None,
stream_kind: None,
network: None,
}
}
pub fn conn(mut self, conn: u64) -> Self {
self.conn = Some(conn);
self
}
pub fn stream(mut self, stream: u64) -> Self {
self.stream = Some(stream);
self
}
pub fn payload_len(mut self, payload_len: u64) -> Self {
self.payload_len = Some(payload_len);
self
}
pub fn frame_len(mut self, frame_len: u64) -> Self {
self.frame_len = Some(frame_len);
self
}
pub fn max_frame_bytes(mut self, max_frame_bytes: u64) -> Self {
self.max_frame_bytes = Some(max_frame_bytes);
self
}
pub fn peer(mut self, peer: &'a str) -> Self {
self.peer = Some(peer);
self
}
pub fn maybe_peer(mut self, peer: Option<&'a str>) -> Self {
self.peer = peer;
self
}
pub fn role(mut self, role: &'static str) -> Self {
self.role = Some(role);
self
}
pub fn phase(mut self, phase: &'static str) -> Self {
self.phase = Some(phase);
self
}
pub fn reason(mut self, reason: &'static str) -> Self {
self.reason = Some(reason);
self
}
pub fn selected_protocol(mut self, selected_protocol: u16) -> Self {
self.selected_protocol = Some(selected_protocol);
self
}
pub fn direction(mut self, direction: &'static str) -> Self {
self.direction = Some(direction);
self
}
pub fn stream_kind(mut self, stream_kind: &'static str) -> Self {
self.stream_kind = Some(stream_kind);
self
}
pub fn network(mut self, network: &'static str) -> Self {
self.network = Some(network);
self
}
fn insert_into(self, row: &mut Map<String, Value>) {
row.insert("event".to_string(), Value::String(self.event.to_string()));
insert_optional_u64(row, "conn", self.conn);
insert_optional_u64(row, "stream", self.stream);
insert_optional_u64(row, "payload_len", self.payload_len);
insert_optional_u64(row, "frame_len", self.frame_len);
insert_optional_u64(row, "max_frame_bytes", self.max_frame_bytes);
insert_optional_str(row, "peer", self.peer);
insert_optional_str(row, "role", self.role);
insert_optional_str(row, "phase", self.phase);
insert_optional_str(row, "reason", self.reason);
insert_optional_u64(
row,
"selected_protocol",
self.selected_protocol.map(u64::from),
);
insert_optional_str(row, "direction", self.direction);
insert_optional_str(row, "stream_kind", self.stream_kind);
insert_optional_str(row, "network", self.network);
}
}
pub fn peer_label(peer_id: &ZakuraPeerId) -> String {
let hash = Blake2bParams::new()
.hash_length(8)
.personal(b"zakura-peer-lbl")
.hash(peer_id.as_bytes());
format!("peer:{}", hex::encode(hash.as_bytes()))
}
pub fn reject_reason_label(reason: ZakuraRejectReason) -> &'static str {
match reason {
ZakuraRejectReason::UnsupportedPreludeVersion => "unsupported_prelude_version",
ZakuraRejectReason::IncompatibleZakuraProtocol => "incompatible_zakura_protocol",
ZakuraRejectReason::WrongNetwork => "wrong_network",
ZakuraRejectReason::WrongChain => "wrong_chain",
ZakuraRejectReason::MissingRequiredCapability => "missing_required_capability",
ZakuraRejectReason::ResourceLimit => "resource_limit",
ZakuraRejectReason::AlreadyConnected => "already_connected",
ZakuraRejectReason::TemporaryUnavailable => "temporary_unavailable",
}
}
pub(crate) fn ordered_send_error_label(
error: &crate::zakura::transport::OrderedSendError,
) -> &'static str {
match error {
crate::zakura::transport::OrderedSendError::Full => "full",
crate::zakura::transport::OrderedSendError::Closed => "closed",
crate::zakura::transport::OrderedSendError::Encode(_) => "encode",
}
}
fn elapsed_micros(elapsed: Duration) -> Value {
let micros = u64::try_from(elapsed.as_micros()).unwrap_or(u64::MAX);
Value::Number(Number::from(micros))
}
fn insert_optional_str(row: &mut Map<String, Value>, key: &'static str, value: Option<&str>) {
row.insert(
key.to_string(),
value.map_or(Value::Null, |value| Value::String(value.to_string())),
);
}
fn insert_optional_u64(row: &mut Map<String, Value>, key: &'static str, value: Option<u64>) {
row.insert(
key.to_string(),
value.map_or(Value::Null, |value| Value::Number(Number::from(value))),
);
}
#[cfg(test)]
mod tests {
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use tokio::sync::mpsc;
use super::*;
#[test]
fn noop_trace_does_not_build_rows() {
let trace = ZakuraTrace::noop();
let called = Arc::new(AtomicBool::new(false));
let called_in_emit = called.clone();
trace.emit_with(CONN_TABLE, |_| {
called_in_emit.store(true, Ordering::SeqCst);
});
assert!(!called.load(Ordering::SeqCst));
}
#[test]
fn full_queue_does_not_build_rows() {
let (tx, _rx) = mpsc::channel(1);
let tracer = JsonlTracer::new(tx);
let trace = ZakuraTrace::new(tracer, "node-full");
trace.emit(CONN_TABLE, ZakuraTraceEvent::new("conn.fill"));
let called = Arc::new(AtomicBool::new(false));
let called_in_emit = called.clone();
trace.emit_with(CONN_TABLE, |_| {
called_in_emit.store(true, Ordering::SeqCst);
});
assert!(
!called.load(Ordering::SeqCst),
"build closure must not run when the queue is full"
);
}
#[test]
fn closed_queue_does_not_build_rows() {
let (tx, rx) = mpsc::channel(1);
let tracer = JsonlTracer::new(tx);
let trace = ZakuraTrace::new(tracer, "node-closed");
drop(rx);
let called = Arc::new(AtomicBool::new(false));
let called_in_emit = called.clone();
trace.emit_with(CONN_TABLE, |_| {
called_in_emit.store(true, Ordering::SeqCst);
});
assert!(
!called.load(Ordering::SeqCst),
"build closure must not run when the queue is closed"
);
assert!(
!trace.is_enabled(),
"trace must report disabled once the receiver is dropped"
);
}
}