1#[cfg(all(target_os = "linux", target_env = "gnu"))]
19use std::ffi::CStr;
20use std::sync::atomic::{AtomicBool, Ordering};
21
22use nautilus_core::{UUID4, consts::NAUTILUS_VERSION_CORE};
23use nautilus_model::{
24 identifiers::TraderId,
25 types::fixed::{FIXED_PRECISION, HIGH_PRECISION_MODE, PRECISION_BYTES},
26};
27use sysinfo::System;
28use ustr::Ustr;
29
30use crate::{enums::LogColor, logging::log_info};
31
32const GIT_COMMIT_LEN: usize = 12;
33static MIMALLOC_REGISTERED: AtomicBool = AtomicBool::new(false);
34
35#[cfg(panic = "abort")]
36const PANIC_STRATEGY: &str = "abort";
37#[cfg(panic = "unwind")]
38const PANIC_STRATEGY: &str = "unwind";
39
40const BUILD_VERSIONS: &[(&str, &str)] = &[
41 ("git_commit", env!("NAUTILUS_BUILD_GIT_COMMIT")),
42 ("rustc", env!("NAUTILUS_BUILD_RUSTC_VERSION")),
43 ("target", env!("NAUTILUS_BUILD_TARGET")),
44 ("profile", env!("NAUTILUS_BUILD_PROFILE")),
45 ("panic", PANIC_STRATEGY),
46 ("cargo_lock", env!("NAUTILUS_BUILD_CARGO_LOCK_CRC32")),
47 #[cfg(all(target_os = "linux", target_env = "gnu"))]
48 ("libc_crate", env!("NAUTILUS_BUILD_LIBC_VERSION")),
49 ("rust_decimal", env!("NAUTILUS_BUILD_RUST_DECIMAL_VERSION")),
50];
51
52#[cfg(feature = "live")]
53const LIVE_VERSIONS: &[(&str, &str)] = &[
54 ("rustls", env!("NAUTILUS_BUILD_RUSTLS_VERSION")),
55 ("aws_lc_rs", env!("NAUTILUS_BUILD_AWS_LC_RS_VERSION")),
56 ("tokio", env!("NAUTILUS_BUILD_TOKIO_VERSION")),
57];
58
59#[cfg(feature = "python")]
60const PYTHON_VERSIONS: &[(&str, &str)] = &[
61 ("pyo3", env!("NAUTILUS_BUILD_PYO3_VERSION")),
62 (
63 "pyo3_async_runtimes",
64 env!("NAUTILUS_BUILD_PYO3_ASYNC_RUNTIMES_VERSION"),
65 ),
66];
67
68pub fn register_allocator_mimalloc() {
73 MIMALLOC_REGISTERED.store(true, Ordering::Release);
74}
75
76#[rustfmt::skip]
78pub fn log_header(trader_id: TraderId, machine_id: &str, instance_id: UUID4, component: Ustr) {
79 let mut sys = System::new();
80 sys.refresh_cpu_all();
81 sys.refresh_memory();
82
83 let c = component;
84
85 let kernel_version = System::kernel_version().map_or(String::new(), |v| format!("kernel-{v} "));
86 let os_version = System::long_os_version().unwrap_or_default();
87 let pid = std::process::id();
88
89 header_sepr(c, "=================================================================");
90 header_sepr(c, " NAUTILUS TRADER - Automated Algorithmic Trading Platform");
91 header_sepr(c, " by Nautech Systems Pty Ltd.");
92 header_sepr(c, " Copyright (C) 2015-2026. All rights reserved.");
93 header_sepr(c, "=================================================================");
94 header_line(c, "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣀⡤⠤⠤⠤⠤⠤⠤⠤⢤⡀⠀⠀⠀⠀⠀⠀");
95 header_line(c, "⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⠤⠖⠚⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⠁⠀⠀⠀⠀⠀⠀");
96 header_line(c, "⠀⠀⠀⠀⠀⠀⢀⣠⠖⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡴⠚⠁⠀⠀⠀⠀⠀⠀⠀");
97 header_line(c, "⠀⠀⠀⠀⢀⡴⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀");
98 header_line(c, "⠀⠀⠀⣠⠏⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⠤⠖⠒⠒⠒⠒⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀");
99 header_line(c, "⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⢀⡖⠋⣠⢴⢪⠞⣩⢟⡭⠵⢤⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀");
100 header_line(c, "⠀⠀⣦⡙⠦⣄⡀⠀⢀⣠⠴⠋⢠⡐⣇⢸⡘⢦⡇⣏⡴⣋⡭⠖⠮⢥⡀⠀⠀⠀⠀⠀⠀⠀");
101 header_line(c, "⠀⠀⢸⡉⠓⠦⠭⠭⠭⠴⣺⠃⠸⣷⢬⣓⣛⠒⠩⣌⢡⡷⢒⣫⠭⣝⡛⠆⠀⠀⠀⠀⠀⠀");
102 header_line(c, "⠀⠀⠀⠙⠦⢤⣀⣠⠤⠞⣡⠞⡆⠺⣭⣭⡷⢇⣷⡻⠡⠾⣛⣒⠦⢤⡙⡆⠀⠀⠀⠀⠀⠀");
103 header_line(c, "⠀⠀⠀⠈⠳⣖⠒⠒⠒⠋⠁⣠⠇⡼⢦⠀⣌⡉⢥⣄⣛⡻⢥⡈⠉⢳⡙⠇⠀⠀⠀⠀⠀⠀");
104 header_line(c, "⠀⠀⠀⠀⠀⠈⠙⣒⣒⣒⣋⡥⠞⠁⣸⠃⡇⠙⢦⢹⠀⠙⡆⢳⢀⡴⠃⠀⠀⠀⠀⠀⠀⠀");
105 header_line(c, "⠀⠀⠀⠀⠀⠀⠀⠈⠙⠒⠦⠤⠴⣚⣡⠞⠁⣠⠏⡼⢀⣠⠇⠞⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀");
106 header_line(c, "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠉⠉⠑⠚⠋⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀");
107 header_sepr(c, "=================================================================");
108 header_sepr(c, " SYSTEM SPECIFICATION");
109 header_sepr(c, "=================================================================");
110
111 if let Some(cpu) = sys.cpus().first() {
112 header_line(c, &format!("CPU architecture: {}", cpu.brand()));
113 header_line(c, &format!("CPU(s): {} @ {} MHz", sys.cpus().len(), cpu.frequency()));
114 } else {
115 header_line(c, "CPU: unknown");
116 }
117 header_line(c, &format!("OS: {kernel_version}{os_version}"));
118
119 log_sysinfo(component);
120
121 header_sepr(c, "=================================================================");
122 header_sepr(c, " IDENTIFIERS");
123 header_sepr(c, "=================================================================");
124 header_line(c, &format!("trader_id: {trader_id}"));
125 header_line(c, &format!("machine_id: {machine_id}"));
126 header_line(c, &format!("instance_id: {instance_id}"));
127 header_line(c, &format!("PID: {pid}"));
128
129 header_sepr(c, "=================================================================");
130 header_sepr(c, " VERSIONING");
131 header_sepr(c, "=================================================================");
132
133 #[cfg(not(feature = "python"))]
134 log_rust_versioning(c);
135
136 #[cfg(feature = "python")]
137 if python_available() {
138 log_python_versioning(c);
139 } else {
140 log_rust_versioning(c);
141 }
142
143 header_sepr(c, "=================================================================");
144}
145
146#[rustfmt::skip]
147fn log_rust_versioning(c: Ustr) {
148 header_line(c, &format!("nautilus_trader: {NAUTILUS_VERSION_CORE}"));
149 log_build_versioning(c);
150 log_optional_versioning(c);
151}
152
153#[cfg(feature = "python")]
154#[rustfmt::skip]
155fn log_python_versioning(c: Ustr) {
156 let package = "nautilus_trader";
157 header_line(c, &format!("{package}: {}", python_package_version(package)));
158 header_line(c, &format!("nautilus_core: {NAUTILUS_VERSION_CORE}"));
159 header_line(c, &format!("python: {}", python_version()));
160 log_build_versioning(c);
161 log_optional_versioning(c);
162
163 for package in ["numpy", "pandas", "msgspec", "pyarrow", "pytz", "uvloop"] {
165 if let Some(version) = python_package_version_opt(package) {
166 header_line(c, &format!("{package}: {version}"));
167 }
168 }
169}
170
171#[rustfmt::skip]
172fn log_build_versioning(c: Ustr) {
173 for (name, version) in build_versions() {
174 header_line(c, &format!("{name}: {version}"));
175 }
176}
177
178#[rustfmt::skip]
179fn log_optional_versioning(c: Ustr) {
180 #[cfg(feature = "build-info-event-store")]
181 header_line(c, &format!("event_store: {}", event_store_version()));
182
183 #[cfg(feature = "live")]
184 for (name, version) in live_versions() {
185 header_line(c, &format!("{name}: {version}"));
186 }
187
188 #[cfg(feature = "python")]
189 for (name, version) in python_versions() {
190 header_line(c, &format!("{name}: {version}"));
191 }
192
193 #[cfg(not(any(
194 feature = "live",
195 feature = "python",
196 feature = "build-info-event-store"
197 )))]
198 let _ = c;
199}
200
201#[cfg(feature = "live")]
202fn live_versions() -> impl Iterator<Item = (&'static str, &'static str)> {
203 LIVE_VERSIONS
204 .iter()
205 .copied()
206 .filter(|(_, version)| !version.is_empty())
207}
208
209#[cfg(feature = "python")]
210fn python_versions() -> impl Iterator<Item = (&'static str, &'static str)> {
211 PYTHON_VERSIONS
212 .iter()
213 .copied()
214 .filter(|(_, version)| !version.is_empty())
215}
216
217#[cfg(feature = "build-info-event-store")]
218fn event_store_version() -> String {
219 let version = env!("NAUTILUS_BUILD_REDB_VERSION");
220 if version.is_empty() {
221 "redb".to_string()
222 } else {
223 format!("redb {version}")
224 }
225}
226
227fn build_versions() -> Vec<(&'static str, String)> {
228 let mut versions = BUILD_VERSIONS
229 .iter()
230 .filter(|(_, version)| !version.is_empty())
231 .map(|(name, version)| (*name, display_version(name, version)))
232 .collect::<Vec<_>>();
233
234 let precision_index = versions
235 .iter()
236 .position(|(name, _)| *name == "panic")
237 .map_or(versions.len(), |index| index + 1);
238 versions.insert(precision_index, ("precision", precision_version()));
239
240 let allocator_index = versions
241 .iter()
242 .position(|(name, _)| *name == "precision")
243 .map_or(versions.len(), |index| index + 1);
244 versions.insert(allocator_index, ("allocator", allocator_version()));
245
246 #[cfg(all(target_os = "linux", target_env = "gnu"))]
247 {
248 if let Some(version) = libc_runtime_version() {
249 let index = versions
250 .iter()
251 .position(|(name, _)| matches!(*name, "cargo_lock" | "libc_crate"))
252 .unwrap_or(versions.len());
253 versions.insert(index, ("libc_runtime", version));
254 }
255 }
256
257 versions
258}
259
260fn precision_version() -> String {
261 let mode = if HIGH_PRECISION_MODE == 1 {
262 "high"
263 } else {
264 "standard"
265 };
266 let bits = PRECISION_BYTES * 8;
267 format!("{mode} ({bits}-bit, {FIXED_PRECISION} dp)")
268}
269
270fn allocator_version() -> String {
271 allocator_version_for(MIMALLOC_REGISTERED.load(Ordering::Acquire))
272}
273
274fn allocator_version_for(mimalloc: bool) -> String {
275 if mimalloc {
276 let version = env!("NAUTILUS_BUILD_MIMALLOC_VERSION");
277 if version.is_empty() {
278 "mimalloc".to_string()
279 } else {
280 format!("mimalloc {version}")
281 }
282 } else {
283 "system".to_string()
284 }
285}
286
287fn display_version(name: &str, version: &str) -> String {
288 match name {
289 "cargo_lock" => version
290 .strip_prefix("crc32:")
291 .unwrap_or(version)
292 .to_string(),
293 "git_commit" => version.get(..GIT_COMMIT_LEN).unwrap_or(version).to_string(),
294 _ => version.to_string(),
295 }
296}
297
298#[cfg(all(target_os = "linux", target_env = "gnu"))]
299#[allow(unsafe_code)]
300fn libc_runtime_version() -> Option<String> {
301 let version = unsafe { libc::gnu_get_libc_version() };
303 if version.is_null() {
304 return None;
305 }
306
307 let version = unsafe { CStr::from_ptr(version) }.to_str().ok()?;
309 Some(format!("glibc {version}"))
310}
311
312#[cfg(feature = "python")]
313#[inline]
314#[allow(unsafe_code)]
315fn python_available() -> bool {
316 unsafe { pyo3::ffi::Py_IsInitialized() != 0 }
319}
320
321#[rustfmt::skip]
323pub fn log_sysinfo(component: Ustr) {
324 let mut sys = System::new();
325 sys.refresh_memory();
326
327 let c = component;
328
329 let ram_total = sys.total_memory();
330 let ram_used = sys.used_memory();
331 let ram_used_p = (ram_used as f64 / ram_total as f64) * 100.0;
332 let ram_avail = ram_total - ram_used;
333 let ram_avail_p = (ram_avail as f64 / ram_total as f64) * 100.0;
334
335 header_sepr(c, "=================================================================");
336 header_sepr(c, " MEMORY USAGE");
337 header_sepr(c, "=================================================================");
338 header_line(c, &format!("RAM-Total: {:.2} GiB", bytes_to_gib(ram_total)));
339 header_line(c, &format!("RAM-Used: {:.2} GiB ({:.2}%)", bytes_to_gib(ram_used), ram_used_p));
340 header_line(c, &format!("RAM-Avail: {:.2} GiB ({:.2}%)", bytes_to_gib(ram_avail), ram_avail_p));
341
342 let swap_total = sys.total_swap();
343 if swap_total > 0 {
344 let swap_used = sys.used_swap();
345 let swap_used_p = (swap_used as f64 / swap_total as f64) * 100.0;
346 let swap_avail = swap_total.saturating_sub(swap_used);
347 let swap_avail_p = (swap_avail as f64 / swap_total as f64) * 100.0;
348 header_line(c, &format!("Swap-Total: {:.2} GiB", bytes_to_gib(swap_total)));
349 header_line(c, &format!("Swap-Used: {:.2} GiB ({:.2}%)", bytes_to_gib(swap_used), swap_used_p));
350 header_line(c, &format!("Swap-Avail: {:.2} GiB ({:.2}%)", bytes_to_gib(swap_avail), swap_avail_p));
351 } else {
352 header_line(c, "Swap: disabled");
353 }
354}
355
356fn header_sepr(c: Ustr, s: &str) {
357 log_info!("{}", s, color = LogColor::Cyan, component = c.as_str());
358}
359
360fn header_line(c: Ustr, s: &str) {
361 log_info!("{}", s, component = c.as_str());
362}
363
364fn bytes_to_gib(b: u64) -> f64 {
365 b as f64 / (2u64.pow(30) as f64)
366}
367
368#[cfg(feature = "python")]
369fn python_package_version(package: &str) -> String {
370 nautilus_core::python::version::get_python_package_version(package)
371}
372
373#[cfg(feature = "python")]
374fn python_package_version_opt(package: &str) -> Option<String> {
375 nautilus_core::python::version::get_python_package_version_opt(package)
376}
377
378#[cfg(feature = "python")]
379fn python_version() -> String {
380 nautilus_core::python::version::get_python_version()
381}
382
383#[cfg(test)]
384mod tests {
385 use rstest::rstest;
386
387 use super::*;
388
389 #[rstest]
390 fn test_build_versions_match_compiled_metadata() {
391 let versions = build_versions();
392
393 for (name, expected) in BUILD_VERSIONS {
394 let expected = (!expected.is_empty()).then(|| display_version(name, expected));
395 assert_eq!(version(&versions, name), expected.as_deref());
396 }
397 }
398
399 #[rstest]
400 fn test_build_modes_match_compiled_configuration() {
401 let versions = build_versions();
402 let panic_index = versions
403 .iter()
404 .position(|(name, _)| *name == "panic")
405 .expect("panic version should be present");
406 let precision_index = versions
407 .iter()
408 .position(|(name, _)| *name == "precision")
409 .expect("precision version should be present");
410 let allocator_index = versions
411 .iter()
412 .position(|(name, _)| *name == "allocator")
413 .expect("allocator version should be present");
414
415 assert_eq!(version(&versions, "panic"), Some(PANIC_STRATEGY));
416 assert_eq!(precision_index, panic_index + 1);
417 assert_eq!(allocator_index, precision_index + 1);
418 assert_eq!(
419 version(&versions, "precision"),
420 Some(precision_version().as_str()),
421 );
422 assert_eq!(version(&versions, "allocator"), Some("system"),);
423 }
424
425 #[rstest]
426 fn test_allocator_version_formats() {
427 let mimalloc = env!("NAUTILUS_BUILD_MIMALLOC_VERSION");
428 let expected = if mimalloc.is_empty() {
429 "mimalloc".to_string()
430 } else {
431 format!("mimalloc {mimalloc}")
432 };
433
434 assert_eq!(allocator_version_for(false), "system");
435 assert_eq!(allocator_version_for(true), expected);
436 }
437
438 #[cfg(feature = "live")]
439 #[rstest]
440 fn test_live_versions_match_compiled_metadata() {
441 let versions = live_versions().collect::<Vec<_>>();
442
443 for (_, expected) in LIVE_VERSIONS {
444 assert!(!expected.is_empty());
445 }
446 assert_eq!(versions, LIVE_VERSIONS);
447 assert_eq!(versions.last().map(|(name, _)| *name), Some("tokio"));
448 }
449
450 #[cfg(feature = "python")]
451 #[rstest]
452 fn test_python_versions_follow_tokio() {
453 let live_versions = live_versions().collect::<Vec<_>>();
454 let python_versions = python_versions().collect::<Vec<_>>();
455
456 for (_, expected) in PYTHON_VERSIONS {
457 assert!(!expected.is_empty());
458 }
459 assert_eq!(python_versions, PYTHON_VERSIONS);
460 assert_eq!(live_versions.last().map(|(name, _)| *name), Some("tokio"));
461 assert_eq!(
462 python_versions
463 .iter()
464 .map(|(name, _)| *name)
465 .collect::<Vec<_>>(),
466 ["pyo3", "pyo3_async_runtimes"],
467 );
468 }
469
470 #[cfg(feature = "build-info-event-store")]
471 #[rstest]
472 fn test_event_store_version_matches_compiled_metadata() {
473 let redb = env!("NAUTILUS_BUILD_REDB_VERSION");
474
475 assert!(!redb.is_empty());
476 assert_eq!(event_store_version(), format!("redb {redb}"));
477 }
478
479 #[rstest]
480 fn test_cargo_lock_fingerprint_format() {
481 const CRC32_HEX_LEN: usize = 8;
482
483 let versions = build_versions();
484 let Some(fingerprint) = version(&versions, "cargo_lock") else {
485 return;
486 };
487 let embedded = env!("NAUTILUS_BUILD_CARGO_LOCK_CRC32");
488 let Some(checksum) = embedded.strip_prefix("crc32:") else {
489 panic!("embedded cargo_lock fingerprint should use the crc32 prefix");
490 };
491
492 assert_eq!(checksum.len(), CRC32_HEX_LEN);
493 assert!(checksum.bytes().all(|byte| byte.is_ascii_hexdigit()));
494 assert_eq!(fingerprint.len(), CRC32_HEX_LEN);
495 assert_eq!(fingerprint, checksum);
496 }
497
498 #[rstest]
499 fn test_git_commit_format() {
500 let versions = build_versions();
501 let Some(commit) = version(&versions, "git_commit") else {
502 return;
503 };
504
505 assert_eq!(commit.len(), GIT_COMMIT_LEN);
506 assert!(commit.bytes().all(|byte| byte.is_ascii_hexdigit()));
507 }
508
509 #[cfg(all(target_os = "linux", target_env = "gnu"))]
510 #[rstest]
511 fn test_libc_runtime_version_reports_glibc() {
512 let version = libc_runtime_version().expect("glibc should report its runtime version");
513 let versions = build_versions();
514 let runtime_index = versions
515 .iter()
516 .position(|(name, _)| *name == "libc_runtime")
517 .expect("libc runtime version should be present");
518 let cargo_lock_index = versions.iter().position(|(name, _)| *name == "cargo_lock");
519 let libc_crate_index = versions.iter().position(|(name, _)| *name == "libc_crate");
520
521 assert!(version.starts_with("glibc "));
522 assert!(
523 version["glibc ".len()..]
524 .bytes()
525 .any(|byte| byte.is_ascii_digit())
526 );
527
528 if let Some(cargo_lock_index) = cargo_lock_index {
529 assert_eq!(cargo_lock_index, runtime_index + 1);
530 }
531
532 if let Some(libc_crate_index) = libc_crate_index {
533 assert_eq!(
534 libc_crate_index,
535 cargo_lock_index.unwrap_or(runtime_index) + 1,
536 );
537 }
538 }
539
540 fn version<'a>(versions: &'a [(&str, String)], name: &str) -> Option<&'a str> {
541 versions
542 .iter()
543 .find(|(candidate, _)| *candidate == name)
544 .map(|(_, version)| version.as_str())
545 }
546}