#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)] #![allow(dead_code)]
use std::io::Write;
use std::sync::Mutex;
use crate::ported::crt::{ColorElements, ColorScheme};
use crate::ported::meter::{
Meter, MeterClass, MeterModeId, Meter_class, Meter_humanUnit, Meter_new, Meter_setMode,
LED_METERMODE, METERMODE_DEFAULT_SUPPORTED, TEXT_METERMODE,
};
use crate::ported::object::ObjectClass;
use crate::ported::richstring::{
RichString, RichString_appendAscii, RichString_appendnAscii, RichString_writeAscii,
};
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum MeterRateStatus {
RATESTATUS_DATA,
RATESTATUS_INIT,
RATESTATUS_NODATA,
RATESTATUS_STALE,
}
struct DiskIOMeterState {
status: MeterRateStatus,
cached_read_diff: f64,
cached_read_diff_str: String,
cached_write_diff: f64,
cached_write_diff_str: String,
cached_num_disks: u64,
cached_utilisation_diff: f64,
cached_utilisation_norm: f64,
}
static DISK_IO_METER_STATE: Mutex<DiskIOMeterState> = Mutex::new(DiskIOMeterState {
status: MeterRateStatus::RATESTATUS_INIT,
cached_read_diff: 0.0,
cached_read_diff_str: String::new(),
cached_write_diff: 0.0,
cached_write_diff_str: String::new(),
cached_num_disks: 0,
cached_utilisation_diff: 0.0,
cached_utilisation_norm: 0.0,
});
#[derive(Default)]
pub struct DiskIOData {
pub totalBytesRead: u64,
pub totalBytesWritten: u64,
pub totalMsTimeSpend: u64,
pub numDisks: u64,
}
const ONE_K: f64 = 1024.0;
static DISK_IO_UPDATE_CACHE: Mutex<(u64, u64, u64, u64)> = Mutex::new((0, 0, 0, 0));
pub fn DiskIOUpdateCache(host: &crate::ported::linux::linuxmachine::LinuxMachine) {
let realtime_ms = host.super_.realtimeMs;
let mut c = DISK_IO_UPDATE_CACHE.lock().unwrap();
let passed_time_ms = realtime_ms.wrapping_sub(c.0);
if passed_time_ms <= 500 {
return;
}
let mut data = DiskIOData::default();
let has_new_data = crate::ported::linux::platform::Platform_getDiskIO(&mut data);
let mut st = DISK_IO_METER_STATE.lock().unwrap();
st.status = if !has_new_data {
MeterRateStatus::RATESTATUS_NODATA
} else if c.0 == 0 {
MeterRateStatus::RATESTATUS_INIT
} else if passed_time_ms > 30000 {
MeterRateStatus::RATESTATUS_STALE
} else {
MeterRateStatus::RATESTATUS_DATA
};
c.0 = realtime_ms;
if !has_new_data {
return;
}
if st.status != MeterRateStatus::RATESTATUS_INIT {
let read_diff = if data.totalBytesRead > c.1 {
(1000 * (data.totalBytesRead - c.1)) / passed_time_ms
} else {
0
};
st.cached_read_diff = read_diff as f64;
st.cached_read_diff_str = Meter_humanUnit(st.cached_read_diff / ONE_K);
let write_diff = if data.totalBytesWritten > c.2 {
(1000 * (data.totalBytesWritten - c.2)) / passed_time_ms
} else {
0
};
st.cached_write_diff = write_diff as f64;
st.cached_write_diff_str = Meter_humanUnit(st.cached_write_diff / ONE_K);
st.cached_num_disks = data.numDisks;
st.cached_utilisation_diff = 0.0;
st.cached_utilisation_norm = 0.0;
if data.totalMsTimeSpend > c.3 {
let diff = data.totalMsTimeSpend - c.3;
st.cached_utilisation_diff = 100.0 * diff as f64 / passed_time_ms as f64;
if data.numDisks > 0 {
st.cached_utilisation_norm =
(diff as f64 / (passed_time_ms as f64 * data.numDisks as f64)).min(1.0);
}
}
}
c.1 = data.totalBytesRead;
c.2 = data.totalBytesWritten;
c.3 = data.totalMsTimeSpend;
}
pub fn DiskIORateMeter_updateValues(this: &mut crate::ported::meter::Meter) {
{
let h = unsafe { &*(this.host as *const crate::ported::linux::linuxmachine::LinuxMachine) };
DiskIOUpdateCache(h);
}
let st = DISK_IO_METER_STATE.lock().unwrap();
this.values[0] = st.cached_read_diff;
this.values[1] = st.cached_write_diff;
match st.status {
MeterRateStatus::RATESTATUS_NODATA => {
this.txtBuffer = "no data".to_string();
return;
}
MeterRateStatus::RATESTATUS_INIT => {
this.txtBuffer = "init".to_string();
return;
}
MeterRateStatus::RATESTATUS_STALE => {
this.txtBuffer = "stale".to_string();
return;
}
MeterRateStatus::RATESTATUS_DATA => {}
}
this.txtBuffer = format!(
"r:{}iB/s w:{}iB/s",
st.cached_read_diff_str, st.cached_write_diff_str
);
}
pub fn DiskIORateMeter_display(this: &Meter, out: &mut RichString) {
let _ = this;
let scheme = ColorScheme::active();
let state = DISK_IO_METER_STATE.lock().unwrap();
match state.status {
MeterRateStatus::RATESTATUS_NODATA => {
RichString_writeAscii(
out,
ColorElements::METER_VALUE_ERROR.packed(scheme),
b"no data",
);
return;
}
MeterRateStatus::RATESTATUS_INIT => {
RichString_writeAscii(
out,
ColorElements::METER_VALUE.packed(scheme),
b"initializing...",
);
return;
}
MeterRateStatus::RATESTATUS_STALE => {
RichString_writeAscii(
out,
ColorElements::METER_VALUE_WARN.packed(scheme),
b"stale data",
);
return;
}
MeterRateStatus::RATESTATUS_DATA => {}
}
RichString_appendAscii(out, ColorElements::METER_TEXT.packed(scheme), b"read: ");
RichString_appendAscii(
out,
ColorElements::METER_VALUE_IOREAD.packed(scheme),
state.cached_read_diff_str.as_bytes(),
);
RichString_appendAscii(
out,
ColorElements::METER_VALUE_IOREAD.packed(scheme),
b"iB/s",
);
RichString_appendAscii(out, ColorElements::METER_TEXT.packed(scheme), b" write: ");
RichString_appendAscii(
out,
ColorElements::METER_VALUE_IOWRITE.packed(scheme),
state.cached_write_diff_str.as_bytes(),
);
RichString_appendAscii(
out,
ColorElements::METER_VALUE_IOWRITE.packed(scheme),
b"iB/s",
);
}
pub fn DiskIOTimeMeter_updateValues(this: &mut crate::ported::meter::Meter) {
{
let h = unsafe { &*(this.host as *const crate::ported::linux::linuxmachine::LinuxMachine) };
DiskIOUpdateCache(h);
}
let st = DISK_IO_METER_STATE.lock().unwrap();
this.values[0] = st.cached_utilisation_norm;
match st.status {
MeterRateStatus::RATESTATUS_NODATA => {
this.txtBuffer = "no data".to_string();
return;
}
MeterRateStatus::RATESTATUS_INIT => {
this.txtBuffer = "init".to_string();
return;
}
MeterRateStatus::RATESTATUS_STALE => {
this.txtBuffer = "stale".to_string();
return;
}
MeterRateStatus::RATESTATUS_DATA => {}
}
let num_disks_str = if st.cached_num_disks > 1 && st.cached_num_disks < 1000 {
format!(" ({}disks)", st.cached_num_disks)
} else {
String::new()
};
this.txtBuffer = format!("{:.1}%{}", st.cached_utilisation_diff, num_disks_str);
}
pub fn DiskIOTimeMeter_display(this: &Meter, out: &mut RichString) {
let _ = this;
let scheme = ColorScheme::active();
let state = DISK_IO_METER_STATE.lock().unwrap();
match state.status {
MeterRateStatus::RATESTATUS_NODATA => {
RichString_writeAscii(
out,
ColorElements::METER_VALUE_ERROR.packed(scheme),
b"no data",
);
return;
}
MeterRateStatus::RATESTATUS_INIT => {
RichString_writeAscii(
out,
ColorElements::METER_VALUE.packed(scheme),
b"initializing...",
);
return;
}
MeterRateStatus::RATESTATUS_STALE => {
RichString_writeAscii(
out,
ColorElements::METER_VALUE_WARN.packed(scheme),
b"stale data",
);
return;
}
MeterRateStatus::RATESTATUS_DATA => {}
}
let color = if state.cached_utilisation_diff > 40.0 {
ColorElements::METER_VALUE_NOTICE
} else {
ColorElements::METER_VALUE
};
let buffer = format!("{:.1}%", state.cached_utilisation_diff);
RichString_appendnAscii(out, color.packed(scheme), buffer.as_bytes(), buffer.len());
RichString_appendAscii(out, ColorElements::METER_TEXT.packed(scheme), b" busy");
if state.cached_num_disks > 1 && state.cached_num_disks < 1000 {
RichString_appendAscii(out, ColorElements::METER_TEXT.packed(scheme), b" (");
let buffer = format!("{}", state.cached_num_disks as u32);
RichString_appendnAscii(
out,
ColorElements::METER_VALUE.packed(scheme),
buffer.as_bytes(),
buffer.len(),
);
RichString_appendAscii(out, ColorElements::METER_TEXT.packed(scheme), b" disks)");
}
}
pub fn DiskIOMeter_display(this: &Meter, out: &mut RichString) {
DiskIORateMeter_display(this, out);
let status = DISK_IO_METER_STATE.lock().unwrap().status;
match status {
MeterRateStatus::RATESTATUS_NODATA
| MeterRateStatus::RATESTATUS_INIT
| MeterRateStatus::RATESTATUS_STALE => return,
MeterRateStatus::RATESTATUS_DATA => {}
}
RichString_appendAscii(
out,
ColorElements::METER_TEXT.packed(ColorScheme::active()),
b"; ",
);
DiskIOTimeMeter_display(this, out);
}
struct DiskIOMeterData {
diskIORateMeter: Meter,
diskIOTimeMeter: Meter,
}
impl DiskIOMeterData {
fn of(this: &mut Meter) -> &mut DiskIOMeterData {
this.meterData
.as_mut()
.and_then(|d| d.downcast_mut::<DiskIOMeterData>())
.expect("DiskIO meter: meterData is not an initialized DiskIOMeterData")
}
}
static DiskIORateMeter_attributes: [i32; 2] = [
ColorElements::METER_VALUE_IOREAD as i32,
ColorElements::METER_VALUE_IOWRITE as i32,
];
static DiskIOTimeMeter_attributes: [i32; 1] = [ColorElements::METER_VALUE_NOTICE as i32];
pub static DiskIORateMeter_class: MeterClass = MeterClass {
super_: ObjectClass {
extends: Some(&Meter_class.super_),
},
display: Some(DiskIORateMeter_display),
init: None,
done: None,
updateMode: None,
updateValues: Some(DiskIORateMeter_updateValues),
draw: None,
getCaption: None,
getUiName: None,
defaultMode: TEXT_METERMODE,
supportedModes: METERMODE_DEFAULT_SUPPORTED,
total: 1.0,
attributes: &DiskIORateMeter_attributes,
name: "DiskIORate",
uiName: "Disk IO Rate",
caption: "Dsk: ",
description: Some("Disk IO read & write bytes per second"),
maxItems: 2,
isMultiColumn: false,
isPercentChart: false,
};
pub static DiskIOTimeMeter_class: MeterClass = MeterClass {
super_: ObjectClass {
extends: Some(&Meter_class.super_),
},
display: Some(DiskIOTimeMeter_display),
init: None,
done: None,
updateMode: None,
updateValues: Some(DiskIOTimeMeter_updateValues),
draw: None,
getCaption: None,
getUiName: None,
defaultMode: TEXT_METERMODE,
supportedModes: METERMODE_DEFAULT_SUPPORTED,
total: 1.0,
attributes: &DiskIOTimeMeter_attributes,
name: "DiskIOTime",
uiName: "Disk IO Time",
caption: "Dsk: ",
description: Some("Disk percent time busy"),
maxItems: 1,
isMultiColumn: false,
isPercentChart: true,
};
pub static DiskIOMeter_class: MeterClass = MeterClass {
super_: ObjectClass {
extends: Some(&Meter_class.super_),
},
display: Some(DiskIOMeter_display),
init: Some(DiskIOMeter_init),
done: Some(DiskIOMeter_done),
updateMode: Some(DiskIOMeter_updateMode),
updateValues: Some(DiskIOMeter_updateValues),
draw: Some(DiskIOMeter_draw),
getCaption: None,
getUiName: None,
defaultMode: TEXT_METERMODE,
supportedModes: METERMODE_DEFAULT_SUPPORTED,
total: 0.0,
attributes: &[],
name: "DiskIO",
uiName: "Disk IO",
caption: "Dsk: ",
description: Some("Disk IO rate & time combined display"),
maxItems: 0,
isMultiColumn: true,
isPercentChart: false,
};
pub fn DiskIOMeter_updateValues(this: &mut Meter) {
let data = DiskIOMeterData::of(this);
let rate_uv = data
.diskIORateMeter
.updateValues
.expect("DiskIOMeter_updateValues: rate sub-meter updateValues");
rate_uv(&mut data.diskIORateMeter);
let time_uv = data
.diskIOTimeMeter
.updateValues
.expect("DiskIOMeter_updateValues: time sub-meter updateValues");
time_uv(&mut data.diskIOTimeMeter);
}
pub fn DiskIOMeter_draw(out: &mut dyn Write, this: &mut Meter, x: i32, y: i32, w: i32) {
if this.mode == TEXT_METERMODE || this.mode == LED_METERMODE {
let draw = DiskIOMeterData::of(this)
.diskIORateMeter
.draw
.expect("DiskIOMeter_draw: rate sub-meter draw");
draw(&mut *out, this, x, y, w);
return;
}
let colwidth = w / 2;
let diff = w % 2;
let data = DiskIOMeterData::of(this);
let rate_draw = data
.diskIORateMeter
.draw
.expect("DiskIOMeter_draw: rate sub-meter draw");
rate_draw(&mut *out, &mut data.diskIORateMeter, x, y, colwidth);
let time_draw = data
.diskIOTimeMeter
.draw
.expect("DiskIOMeter_draw: time sub-meter draw");
time_draw(
&mut *out,
&mut data.diskIOTimeMeter,
x + colwidth + diff,
y,
colwidth,
);
}
pub fn DiskIOMeter_init(this: &mut Meter) {
if this.meterData.is_none() {
let host = this.host;
this.meterData = Some(Box::new(DiskIOMeterData {
diskIORateMeter: Meter_new(host, 0, &DiskIORateMeter_class),
diskIOTimeMeter: Meter_new(host, 0, &DiskIOTimeMeter_class),
}));
}
}
pub fn DiskIOMeter_updateMode(this: &mut Meter, mode: MeterModeId) {
this.mode = mode;
let data = DiskIOMeterData::of(this);
Meter_setMode(&mut data.diskIORateMeter, mode);
Meter_setMode(&mut data.diskIOTimeMeter, mode);
let h = data.diskIORateMeter.h.max(data.diskIOTimeMeter.h);
this.h = h;
}
pub fn DiskIOMeter_done(this: &mut Meter) {
this.meterData = None;
}
#[cfg(test)]
mod tests {
use super::*;
static TEST_LOCK: Mutex<()> = Mutex::new(());
fn text(r: &RichString) -> String {
(0..r.chlen as usize).map(|i| r.chptr[i].chars).collect()
}
fn dummy() -> Meter {
Meter::empty()
}
fn set_state(status: MeterRateStatus, read: &str, write: &str, util: f64, disks: u64) {
let mut s = DISK_IO_METER_STATE.lock().unwrap();
s.status = status;
s.cached_read_diff_str = read.to_string();
s.cached_write_diff_str = write.to_string();
s.cached_utilisation_diff = util;
s.cached_num_disks = disks;
}
#[test]
fn rate_display_status_words() {
let _g = TEST_LOCK.lock().unwrap();
set_state(MeterRateStatus::RATESTATUS_NODATA, "", "", 0.0, 0);
let mut out = RichString::new();
DiskIORateMeter_display(&dummy(), &mut out);
assert_eq!(text(&out), "no data");
set_state(MeterRateStatus::RATESTATUS_INIT, "", "", 0.0, 0);
let mut out = RichString::new();
DiskIORateMeter_display(&dummy(), &mut out);
assert_eq!(text(&out), "initializing...");
set_state(MeterRateStatus::RATESTATUS_STALE, "", "", 0.0, 0);
let mut out = RichString::new();
DiskIORateMeter_display(&dummy(), &mut out);
assert_eq!(text(&out), "stale data");
}
#[test]
fn rate_display_data_line() {
let _g = TEST_LOCK.lock().unwrap();
set_state(MeterRateStatus::RATESTATUS_DATA, "1.23G", "45.6M", 0.0, 0);
let mut out = RichString::new();
DiskIORateMeter_display(&dummy(), &mut out);
assert_eq!(text(&out), "read: 1.23GiB/s write: 45.6MiB/s");
}
#[test]
fn time_display_status_words() {
let _g = TEST_LOCK.lock().unwrap();
set_state(MeterRateStatus::RATESTATUS_NODATA, "", "", 0.0, 0);
let mut out = RichString::new();
DiskIOTimeMeter_display(&dummy(), &mut out);
assert_eq!(text(&out), "no data");
}
#[test]
fn time_display_busy_no_disk_suffix() {
let _g = TEST_LOCK.lock().unwrap();
set_state(MeterRateStatus::RATESTATUS_DATA, "", "", 12.34, 1);
let mut out = RichString::new();
DiskIOTimeMeter_display(&dummy(), &mut out);
assert_eq!(text(&out), "12.3% busy");
}
#[test]
fn time_display_busy_with_disk_suffix() {
let _g = TEST_LOCK.lock().unwrap();
set_state(MeterRateStatus::RATESTATUS_DATA, "", "", 87.65, 4);
let mut out = RichString::new();
DiskIOTimeMeter_display(&dummy(), &mut out);
assert_eq!(text(&out), "87.7% busy (4 disks)");
}
#[test]
fn time_display_suffix_suppressed_at_1000_disks() {
let _g = TEST_LOCK.lock().unwrap();
set_state(MeterRateStatus::RATESTATUS_DATA, "", "", 5.0, 1000);
let mut out = RichString::new();
DiskIOTimeMeter_display(&dummy(), &mut out);
assert_eq!(text(&out), "5.0% busy");
}
#[test]
fn combined_display_data_joins_with_semicolon() {
let _g = TEST_LOCK.lock().unwrap();
set_state(MeterRateStatus::RATESTATUS_DATA, "10K", "20K", 33.3, 2);
let mut out = RichString::new();
DiskIOMeter_display(&dummy(), &mut out);
assert_eq!(
text(&out),
"read: 10KiB/s write: 20KiB/s; 33.3% busy (2 disks)"
);
}
#[test]
fn combined_display_nondata_is_rate_word_only() {
let _g = TEST_LOCK.lock().unwrap();
set_state(MeterRateStatus::RATESTATUS_STALE, "", "", 0.0, 0);
let mut out = RichString::new();
DiskIOMeter_display(&dummy(), &mut out);
assert_eq!(text(&out), "stale data");
}
#[test]
fn rate_meter_update_first_sample_status() {
use crate::ported::linux::linuxmachine::LinuxMachine;
use crate::ported::machine::Machine;
use crate::ported::meter::Meter;
let host = Box::leak(Box::new(LinuxMachine {
super_: Machine {
realtimeMs: 1000,
..Default::default()
},
..Default::default()
}));
let mut m = Meter {
values: vec![0.0; 2],
host: &host.super_ as *const crate::ported::machine::Machine,
..Meter::empty()
};
super::DiskIORateMeter_updateValues(&mut m);
assert!(m.txtBuffer == "init" || m.txtBuffer == "no data" || m.txtBuffer.starts_with("r:"));
assert!(m.values[0] >= 0.0 && m.values[1] >= 0.0);
}
}