///|
/// Transport mode used for operational metrics.
pub(all) enum MetricsTransport {
MetricsRtu
MetricsAscii
MetricsTcp
MetricsTls
MetricsVirtual
} derive(Debug, Eq)
///|
/// Request outcome accumulated by a transport lane.
pub(all) struct TransportMetricWindow {
transport : MetricsTransport
window : Int
mut requests : Int
mut successes : Int
mut failures : Int
mut bytes_sent : Int
mut bytes_received : Int
mut latency_total : Int
mut latency_max : Int
mut last_timestamp : Int
}
///|
pub fn TransportMetricWindow::new(
transport : MetricsTransport,
window : Int,
) -> Result[TransportMetricWindow, ModbusError] {
if window < 1 {
Err(InvalidData)
} else {
Ok({
transport,
window,
requests: 0,
successes: 0,
failures: 0,
bytes_sent: 0,
bytes_received: 0,
latency_total: 0,
latency_max: 0,
last_timestamp: 0,
})
}
}
///|
pub fn TransportMetricWindow::transport(
self : TransportMetricWindow,
) -> MetricsTransport {
self.transport
}
///|
pub fn TransportMetricWindow::window(self : TransportMetricWindow) -> Int {
self.window
}
///|
pub fn TransportMetricWindow::requests(self : TransportMetricWindow) -> Int {
self.requests
}
///|
pub fn TransportMetricWindow::successes(self : TransportMetricWindow) -> Int {
self.successes
}
///|
pub fn TransportMetricWindow::failures(self : TransportMetricWindow) -> Int {
self.failures
}
///|
pub fn TransportMetricWindow::bytes_sent(self : TransportMetricWindow) -> Int {
self.bytes_sent
}
///|
pub fn TransportMetricWindow::bytes_received(
self : TransportMetricWindow,
) -> Int {
self.bytes_received
}
///|
pub fn TransportMetricWindow::average_latency(
self : TransportMetricWindow,
) -> Int {
if self.requests == 0 {
0
} else {
self.latency_total / self.requests
}
}
///|
pub fn TransportMetricWindow::maximum_latency(
self : TransportMetricWindow,
) -> Int {
self.latency_max
}
///|
pub fn TransportMetricWindow::success_rate(self : TransportMetricWindow) -> Int {
if self.requests == 0 {
0
} else {
self.successes * 100 / self.requests
}
}
///|
pub fn TransportMetricWindow::record(
self : TransportMetricWindow,
timestamp : Int,
success : Bool,
latency : Int,
sent : Int,
received : Int,
) -> Result[Unit, ModbusError] {
if timestamp < self.last_timestamp || latency < 0 || sent < 0 || received < 0 {
return Err(InvalidData)
}
self.requests += 1
if success {
self.successes += 1
} else {
self.failures += 1
}
self.bytes_sent += sent
self.bytes_received += received
self.latency_total += latency
if latency > self.latency_max {
self.latency_max = latency
}
self.last_timestamp = timestamp
Ok(())
}
///|
pub fn TransportMetricWindow::reset(self : TransportMetricWindow) -> Unit {
self.requests = 0
self.successes = 0
self.failures = 0
self.bytes_sent = 0
self.bytes_received = 0
self.latency_total = 0
self.latency_max = 0
}
///|
pub fn metrics_transport_name(transport : MetricsTransport) -> String {
match transport {
MetricsRtu => "rtu"
MetricsAscii => "ascii"
MetricsTcp => "tcp"
MetricsTls => "tls"
MetricsVirtual => "virtual"
}
}
///|
/// Fleet transport metrics registry.
pub(all) struct TransportMetricsRegistry {
windows : Array[TransportMetricWindow]
max_windows : Int
}
///|
pub fn TransportMetricsRegistry::new(
max_windows? : Int = 32,
window? : Int = 60000,
) -> Result[TransportMetricsRegistry, ModbusError] {
if max_windows < 1 || window < 1 {
return Err(InvalidData)
}
let registry = { windows: [], max_windows }
for
transport in [
MetricsRtu,
MetricsAscii,
MetricsTcp,
MetricsTls,
MetricsVirtual,
] {
let metric = match TransportMetricWindow::new(transport, window) {
Ok(value) => value
Err(error) => return Err(error)
}
registry.windows.push(metric)
}
Ok(registry)
}
///|
pub fn TransportMetricsRegistry::count(self : TransportMetricsRegistry) -> Int {
self.windows.length()
}
///|
pub fn TransportMetricsRegistry::window(
self : TransportMetricsRegistry,
transport : MetricsTransport,
) -> TransportMetricWindow? {
for window in self.windows {
if window.transport() == transport {
return Some(window)
}
}
None
}
///|
pub fn TransportMetricsRegistry::record(
self : TransportMetricsRegistry,
transport : MetricsTransport,
timestamp : Int,
success : Bool,
latency : Int,
sent : Int,
received : Int,
) -> Result[Unit, ModbusError] {
match self.window(transport) {
Some(window) => window.record(timestamp, success, latency, sent, received)
None => Err(InvalidData)
}
}
///|
pub fn TransportMetricsRegistry::total_requests(
self : TransportMetricsRegistry,
) -> Int {
let mut total = 0
for window in self.windows {
total += window.requests()
}
total
}
///|
pub fn TransportMetricsRegistry::total_failures(
self : TransportMetricsRegistry,
) -> Int {
let mut total = 0
for window in self.windows {
total += window.failures()
}
total
}
///|
pub fn TransportMetricsRegistry::export_lines(
self : TransportMetricsRegistry,
) -> Array[String] {
let lines : Array[String] = []
for window in self.windows {
lines.push(
"transport=" +
metrics_transport_name(window.transport()) +
" requests=" +
window.requests().to_string() +
" success_rate=" +
window.success_rate().to_string() +
" avg_latency=" +
window.average_latency().to_string() +
" max_latency=" +
window.maximum_latency().to_string() +
" tx_bytes=" +
window.bytes_sent().to_string() +
" rx_bytes=" +
window.bytes_received().to_string(),
)
}
lines
}