tape: implement read_volume_statistics
This commit is contained in:
parent
470f1c798a
commit
f8ccbfdedd
@ -122,6 +122,31 @@ fn tape_alert_flags(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[api(
|
||||
input: {
|
||||
properties: {
|
||||
device: {
|
||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
)]
|
||||
/// Read volume statistics
|
||||
fn volume_statistics(
|
||||
device: Option<String>,
|
||||
) -> Result<(), Error> {
|
||||
|
||||
let result = proxmox::try_block!({
|
||||
let mut handle = get_tape_handle(device)?;
|
||||
handle.volume_statistics()
|
||||
}).map_err(|err: Error| err.to_string());
|
||||
|
||||
println!("{}", serde_json::to_string_pretty(&result)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
|
||||
// check if we are user root or backup
|
||||
@ -157,6 +182,10 @@ fn main() -> Result<(), Error> {
|
||||
"tape-alert-flags",
|
||||
CliCommand::new(&API_METHOD_TAPE_ALERT_FLAGS)
|
||||
)
|
||||
.insert(
|
||||
"volume-statistics",
|
||||
CliCommand::new(&API_METHOD_VOLUME_STATISTICS)
|
||||
)
|
||||
;
|
||||
|
||||
let mut rpcenv = CliEnvironment::new();
|
||||
|
@ -19,9 +19,11 @@ use crate::{
|
||||
TapeRead,
|
||||
TapeWrite,
|
||||
TapeAlertFlags,
|
||||
Lp17VolumeStatistics,
|
||||
read_mam_attributes,
|
||||
mam_extract_media_usage,
|
||||
read_tape_alert_flags,
|
||||
read_volume_statistics,
|
||||
drive::{
|
||||
LinuxTapeDrive,
|
||||
TapeDriver,
|
||||
@ -323,6 +325,25 @@ impl LinuxTapeHandle {
|
||||
.map_err(|err| format_err!("{}", err))
|
||||
.map(|bits| TapeAlertFlags::from_bits_truncate(bits))
|
||||
}
|
||||
|
||||
/// Read Volume Statistics
|
||||
///
|
||||
/// Note: Only 'root' user may run RAW SG commands, so we need to
|
||||
/// spawn setuid binary 'sg-tape-cmd'.
|
||||
pub fn volume_statistics(&mut self) -> Result<Lp17VolumeStatistics, Error> {
|
||||
|
||||
if nix::unistd::Uid::effective().is_root() {
|
||||
return read_volume_statistics(&mut self.file);
|
||||
}
|
||||
|
||||
let mut command = std::process::Command::new(
|
||||
"/usr/lib/x86_64-linux-gnu/proxmox-backup/sg-tape-cmd");
|
||||
command.args(&["volume-statistics"]);
|
||||
command.stdin(unsafe { std::process::Stdio::from_raw_fd(self.file.as_raw_fd())});
|
||||
let output = run_command(command, None)?;
|
||||
let result: Result<Lp17VolumeStatistics, String> = serde_json::from_str(&output)?;
|
||||
result.map_err(|err| format_err!("{}", err))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -4,6 +4,9 @@ mod linux_mtio;
|
||||
mod tape_alert_flags;
|
||||
pub use tape_alert_flags::*;
|
||||
|
||||
mod volume_statistics;
|
||||
pub use volume_statistics::*;
|
||||
|
||||
pub mod linux_tape;
|
||||
|
||||
mod mam;
|
||||
|
266
src/tape/drive/volume_statistics.rs
Normal file
266
src/tape/drive/volume_statistics.rs
Normal file
@ -0,0 +1,266 @@
|
||||
use std::io::Read;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use anyhow::{bail, format_err, Error};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use endian_trait::Endian;
|
||||
|
||||
use proxmox::tools::io::ReadExt;
|
||||
|
||||
use crate::{
|
||||
tape::{
|
||||
sgutils2::SgRaw,
|
||||
},
|
||||
};
|
||||
|
||||
/// SCSI command to query volume statistics
|
||||
///
|
||||
/// CDB: LOG SENSE / LP17h Volume Statistics
|
||||
///
|
||||
/// The Volume Statistics log page is included in Ultrium 5 and later
|
||||
/// drives.
|
||||
pub fn read_volume_statistics<F: AsRawFd>(file: &mut F) -> Result<Lp17VolumeStatistics, Error> {
|
||||
|
||||
let data = sg_read_volume_statistics(file)?;
|
||||
|
||||
decode_volume_statistics(&data)
|
||||
}
|
||||
|
||||
fn sg_read_volume_statistics<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
|
||||
|
||||
let buffer_size = 8192;
|
||||
let mut sg_raw = SgRaw::new(file, buffer_size)?;
|
||||
|
||||
// Note: We cannjot use LP 2Eh TapeAlerts, because that clears flags on read.
|
||||
// Instead, we use LP 12h TapeAlert Response. which does not clear the flags.
|
||||
|
||||
let mut cmd = Vec::new();
|
||||
cmd.push(0x4D); // LOG SENSE
|
||||
cmd.push(0);
|
||||
cmd.push((1<<6) | 0x17); // Volume Statistics log page
|
||||
cmd.push(0); // Subpage 0
|
||||
cmd.push(0);
|
||||
cmd.push(0);
|
||||
cmd.push(0);
|
||||
cmd.push((buffer_size >> 8) as u8); cmd.push(0); // alloc len
|
||||
cmd.push(0u8); // control byte
|
||||
|
||||
sg_raw.do_command(&cmd)
|
||||
.map_err(|err| format_err!("read tape volume statistics failed - {}", err))
|
||||
.map(|v| v.to_vec())
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Endian)]
|
||||
struct LpParameterHeader {
|
||||
parameter_code: u16,
|
||||
control: u8,
|
||||
parameter_len: u8,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
pub struct Lp17VolumeStatistics {
|
||||
volume_mounts: u64,
|
||||
volume_datasets_written: u64,
|
||||
volume_recovered_write_data_errors: u64,
|
||||
volume_unrecovered_write_data_errors: u64,
|
||||
volume_write_servo_errors: u64,
|
||||
volume_unrecovered_write_servo_errors: u64,
|
||||
volume_datasets_read: u64,
|
||||
volume_recovered_read_errors: u64,
|
||||
volume_unrecovered_read_errors: u64,
|
||||
last_mount_unrecovered_write_errors: u64,
|
||||
last_mount_unrecovered_read_errors: u64,
|
||||
last_mount_bytes_written: u64,
|
||||
last_mount_bytes_read: u64,
|
||||
lifetime_bytes_written: u64,
|
||||
lifetime_bytes_read: u64,
|
||||
last_load_write_compression_ratio: u64,
|
||||
last_load_read_compression_ratio: u64,
|
||||
medium_mount_time: u64,
|
||||
medium_ready_time: u64,
|
||||
total_native_capacity: u64,
|
||||
total_used_native_capacity: u64,
|
||||
write_protect: bool,
|
||||
worm: bool,
|
||||
beginning_of_medium_passes: u64,
|
||||
middle_of_tape_passes: u64,
|
||||
serial: String,
|
||||
}
|
||||
|
||||
//impl Default for Lp17VolumeStatistics {
|
||||
|
||||
fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error> {
|
||||
|
||||
|
||||
let read_be_counter = |reader: &mut &[u8], len: u8| {
|
||||
let len = len as usize;
|
||||
|
||||
if len == 0 || len > 8 {
|
||||
bail!("invalid conter size '{}'", len);
|
||||
}
|
||||
let mut buffer = [0u8; 8];
|
||||
reader.read_exact(&mut buffer[..len])?;
|
||||
|
||||
let mut value: u64 = 0;
|
||||
|
||||
for i in 0..len {
|
||||
value = value << 8;
|
||||
value = value | buffer[i] as u64;
|
||||
}
|
||||
|
||||
Ok(value)
|
||||
};
|
||||
|
||||
proxmox::try_block!({
|
||||
if !((data[0] & 0x7f) == 0x17 && data[1] == 0) {
|
||||
bail!("invalid response");
|
||||
}
|
||||
|
||||
let mut reader = &data[2..];
|
||||
|
||||
let page_len: u16 = unsafe { reader.read_be_value()? };
|
||||
|
||||
if (page_len as usize + 4) != data.len() {
|
||||
bail!("invalid page length");
|
||||
}
|
||||
|
||||
let mut stat = Lp17VolumeStatistics::default();
|
||||
let mut page_valid = false;
|
||||
|
||||
loop {
|
||||
if reader.is_empty() {
|
||||
break;
|
||||
}
|
||||
let head: LpParameterHeader = unsafe { reader.read_be_value()? };
|
||||
|
||||
match head.parameter_code {
|
||||
0x0000 => {
|
||||
let value: u64 = read_be_counter(&mut reader, head.parameter_len)?;
|
||||
if value == 0 {
|
||||
bail!("page-valid flag not set");
|
||||
}
|
||||
page_valid = true;
|
||||
}
|
||||
0x0001 => {
|
||||
stat.volume_mounts =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0002 => {
|
||||
stat.volume_datasets_written =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0003 => {
|
||||
stat.volume_recovered_write_data_errors =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0004 => {
|
||||
stat.volume_unrecovered_write_data_errors =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0005 => {
|
||||
stat.volume_write_servo_errors =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0006 => {
|
||||
stat.volume_unrecovered_write_servo_errors =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0007 => {
|
||||
stat.volume_datasets_read =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0008 => {
|
||||
stat.volume_recovered_read_errors =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0009 => {
|
||||
stat.volume_unrecovered_read_errors =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x000C => {
|
||||
stat.last_mount_unrecovered_write_errors =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x000D => {
|
||||
stat.last_mount_unrecovered_read_errors =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x000E => {
|
||||
stat.last_mount_bytes_written =
|
||||
read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
|
||||
}
|
||||
0x000F => {
|
||||
stat.last_mount_bytes_read =
|
||||
read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
|
||||
}
|
||||
0x0010 => {
|
||||
stat.lifetime_bytes_written =
|
||||
read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
|
||||
}
|
||||
0x0011 => {
|
||||
stat.lifetime_bytes_read =
|
||||
read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
|
||||
}
|
||||
0x0012 => {
|
||||
stat.last_load_write_compression_ratio =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0013 => {
|
||||
stat.last_load_read_compression_ratio =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0014 => {
|
||||
stat.medium_mount_time =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0015 => {
|
||||
stat.medium_ready_time =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0016 => {
|
||||
stat.total_native_capacity =
|
||||
read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
|
||||
}
|
||||
0x0017 => {
|
||||
stat.total_used_native_capacity =
|
||||
read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
|
||||
}
|
||||
0x0040 => {
|
||||
let data = reader.read_exact_allocated(head.parameter_len as usize)?;
|
||||
stat.serial = String::from_utf8_lossy(&data).to_string();
|
||||
}
|
||||
0x0080 => {
|
||||
let value = read_be_counter(&mut reader, head.parameter_len)?;
|
||||
if value == 1 {
|
||||
stat.write_protect = true;
|
||||
}
|
||||
}
|
||||
0x0081 => {
|
||||
let value = read_be_counter(&mut reader, head.parameter_len)?;
|
||||
if value == 1 {
|
||||
stat.worm = true;
|
||||
}
|
||||
}
|
||||
0x0101 => {
|
||||
stat.beginning_of_medium_passes =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0102 => {
|
||||
stat.middle_of_tape_passes =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
_ => {
|
||||
reader.read_exact_allocated(head.parameter_len as usize)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !page_valid {
|
||||
bail!("missing page-valid parameter");
|
||||
}
|
||||
|
||||
Ok(stat)
|
||||
|
||||
}).map_err(|err| format_err!("decode volume statistics failed - {}", err))
|
||||
}
|
Loading…
Reference in New Issue
Block a user