Files
btest-rs/src/syslog_logger.rs
Siavash Sameni b3c12b7f8b perf: eliminate redundant allocations and computations (Sprint 1)
This commit applies eight low-risk internal optimizations identified
in the performance audit. No wire protocol changes — 100% MikroTik
compatible.

Changes:
- ecsrp5.rs: Cache WCurve in a global LazyLock, eliminating the
  expensive BigUint modular square root recomputation on every
  EC-SRP5 authentication. Also optimize the local hex::encode
  module to use a single pre-allocated String instead of N format!
  allocations.

- server.rs: Deduplicate Instant::now() calls in the TCP TX hot
  loop, caching the result at the top of each iteration.

- csv_output.rs: Hold the CSV file handle open in a static
  Mutex<Option<(String, File)>> instead of reopening the file on
  every write_result call. Add explicit flush after each write.

- server_pro/user_db.rs: Replace hand-rolled Gregorian calendar
  math (30+ lines looping from 1970) with chrono::Local::now().
  Optimize hash_password() to write username:password directly
  into the SHA256 hasher and hex-encode with a pre-allocated
  String.

- server_pro/enforcer.rs: Replace allocating error string matching
  (format!({}, e).as_str().contains(...)) with direct
  QuotaError variant matching. Pass ip_str into flush_to_db()
  to avoid a per-call ip.to_string().

- syslog_logger.rs: Move timestamp formatting outside the global
  std::sync::Mutex to reduce lock hold time. Replace manual
  calendar arithmetic with chrono::Local::now().format().

New dependency: chrono (already pulled in transitively by rusqlite).
2026-04-30 20:45:56 +04:00

118 lines
3.3 KiB
Rust

//! Syslog integration for btest-rs server mode.
//!
//! Sends structured log events to a remote syslog server via UDP (RFC 5424).
//! Events: auth success/failure, test start/stop, speed results.
use std::net::UdpSocket;
use std::sync::Mutex;
static SYSLOG: Mutex<Option<SyslogSender>> = Mutex::new(None);
struct SyslogSender {
socket: UdpSocket,
target: String,
hostname: String,
}
/// Initialize the global syslog sender.
/// `target` is the syslog server address, e.g. "192.168.1.1:514".
pub fn init(target: &str) -> std::io::Result<()> {
let socket = UdpSocket::bind("0.0.0.0:0")?;
let hostname = hostname::get()
.map(|h| h.to_string_lossy().to_string())
.unwrap_or_else(|_| "btest-rs".to_string());
let sender = SyslogSender {
socket,
target: target.to_string(),
hostname,
};
*SYSLOG.lock().unwrap() = Some(sender);
tracing::info!("Syslog enabled, sending to {}", target);
Ok(())
}
/// Send a syslog message with the given severity and message.
/// Severity: 6=info, 4=warning, 3=error
fn send(severity: u8, msg: &str) {
// Format timestamp outside the lock to minimize contention
let priority = 128 + severity;
let timestamp = bsd_timestamp();
let guard = SYSLOG.lock().unwrap();
if let Some(ref sender) = *guard {
let syslog_msg = format!(
"<{}>{} {} btest-rs: {}",
priority, timestamp, sender.hostname, msg,
);
let _ = sender.socket.send_to(syslog_msg.as_bytes(), &sender.target);
}
}
fn bsd_timestamp() -> String {
// RFC 3164 format: "Mon DD HH:MM:SS" (no year)
chrono::Local::now().format("%b %e %H:%M:%S").to_string()
}
// --- Public logging functions ---
pub fn auth_success(peer: &str, username: &str, auth_type: &str) {
let msg = format!(
"AUTH_SUCCESS peer={} user={} type={}",
peer, username, auth_type,
);
tracing::info!("{}", msg);
send(6, &msg);
}
pub fn auth_failure(peer: &str, username: &str, auth_type: &str, reason: &str) {
let msg = format!(
"AUTH_FAILURE peer={} user={} type={} reason={}",
peer, username, auth_type, reason,
);
tracing::warn!("{}", msg);
send(4, &msg);
}
pub fn test_start(peer: &str, proto: &str, direction: &str, conn_count: u8) {
let msg = format!(
"TEST_START peer={} proto={} dir={} connections={}",
peer, proto, direction, conn_count.max(1),
);
tracing::info!("{}", msg);
send(6, &msg);
}
pub fn test_end(
peer: &str,
proto: &str,
direction: &str,
total_tx: u64,
total_rx: u64,
total_lost: u64,
duration_secs: u32,
) {
let tx_mbps = if duration_secs > 0 {
total_tx as f64 * 8.0 / duration_secs as f64 / 1_000_000.0
} else {
0.0
};
let rx_mbps = if duration_secs > 0 {
total_rx as f64 * 8.0 / duration_secs as f64 / 1_000_000.0
} else {
0.0
};
let msg = format!(
"TEST_END peer={} proto={} dir={} duration={}s tx_avg={:.2}Mbps rx_avg={:.2}Mbps tx_bytes={} rx_bytes={} lost={}",
peer, proto, direction, duration_secs, tx_mbps, rx_mbps, total_tx, total_rx, total_lost,
);
tracing::info!("{}", msg);
send(6, &msg);
}
/// Check if syslog is enabled.
pub fn is_enabled() -> bool {
SYSLOG.lock().unwrap().is_some()
}