[pbs-devel] [PATCH proxmox-backup] tape: save 'bytes used' in tape inventory

Dominik Csapak d.csapak at proxmox.com
Mon May 13 12:46:09 CEST 2024


and show them on the ui. This can help uses with seeing how much a tape
is used.

The value is updated on 'commit' and when the tape is changed during a
backup.

For drives not supporting the volume statistics, this is simply skipped.

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
 pbs-api-types/src/tape/media.rs |  3 +++
 src/api2/tape/media.rs          |  3 +++
 src/tape/drive/lto/mod.rs       |  4 ++++
 src/tape/drive/mod.rs           |  3 +++
 src/tape/drive/virtual_tape.rs  |  4 ++++
 src/tape/inventory.rs           | 30 ++++++++++++++++++++++++++++++
 src/tape/media_pool.rs          | 32 +++++++++++++++++++++++++++++---
 src/tape/pool_writer/mod.rs     | 16 +++++++++++++++-
 www/tape/TapeInventory.js       |  7 +++++++
 9 files changed, 98 insertions(+), 4 deletions(-)

diff --git a/pbs-api-types/src/tape/media.rs b/pbs-api-types/src/tape/media.rs
index 6792cd3c9..6227f4634 100644
--- a/pbs-api-types/src/tape/media.rs
+++ b/pbs-api-types/src/tape/media.rs
@@ -81,6 +81,9 @@ pub struct MediaListEntry {
     /// Media Pool
     #[serde(skip_serializing_if = "Option::is_none")]
     pub pool: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    /// Bytes currently used
+    pub bytes_used: Option<u64>,
 }
 
 #[api(
diff --git a/src/api2/tape/media.rs b/src/api2/tape/media.rs
index 07bed86a2..159906ba7 100644
--- a/src/api2/tape/media.rs
+++ b/src/api2/tape/media.rs
@@ -204,6 +204,7 @@ pub async fn list_media(
                 media_set_uuid,
                 media_set_name,
                 seq_nr,
+                bytes_used: media.bytes_used(),
             });
         }
     }
@@ -232,6 +233,7 @@ pub async fn list_media(
                 media_set_ctime: None,
                 seq_nr: None,
                 pool: None,
+                bytes_used: inventory.get_media_bytes_used(&media_id.label.uuid),
             });
         }
     }
@@ -279,6 +281,7 @@ pub async fn list_media(
             media_set_uuid,
             media_set_name,
             seq_nr,
+            bytes_used: inventory.get_media_bytes_used(&media_id.label.uuid),
         });
     }
 
diff --git a/src/tape/drive/lto/mod.rs b/src/tape/drive/lto/mod.rs
index 7de07cf95..f3143c907 100644
--- a/src/tape/drive/lto/mod.rs
+++ b/src/tape/drive/lto/mod.rs
@@ -268,6 +268,10 @@ impl TapeDriver for LtoTapeHandle {
         }
         Ok(())
     }
+
+    fn get_volume_statistics(&mut self) -> Result<pbs_api_types::Lp17VolumeStatistics, Error> {
+        self.volume_statistics()
+    }
 }
 
 fn run_sg_tape_cmd(subcmd: &str, args: &[&str], fd: RawFd) -> Result<String, Error> {
diff --git a/src/tape/drive/mod.rs b/src/tape/drive/mod.rs
index 39602461f..b21a62d20 100644
--- a/src/tape/drive/mod.rs
+++ b/src/tape/drive/mod.rs
@@ -242,6 +242,9 @@ pub trait TapeDriver {
         }
         Ok(())
     }
+
+    /// Returns volume statistics from a loaded tape
+    fn get_volume_statistics(&mut self) -> Result<pbs_api_types::Lp17VolumeStatistics, Error>;
 }
 
 /// A boxed implementor of [`MediaChange`].
diff --git a/src/tape/drive/virtual_tape.rs b/src/tape/drive/virtual_tape.rs
index b13c58c4e..c183e2681 100644
--- a/src/tape/drive/virtual_tape.rs
+++ b/src/tape/drive/virtual_tape.rs
@@ -461,6 +461,10 @@ impl TapeDriver for VirtualTapeHandle {
         let status = VirtualDriveStatus { current_tape: None };
         self.store_status(&status)
     }
+
+    fn get_volume_statistics(&mut self) -> Result<pbs_api_types::Lp17VolumeStatistics, Error> {
+        Ok(Default::default())
+    }
 }
 
 impl MediaChange for VirtualTapeHandle {
diff --git a/src/tape/inventory.rs b/src/tape/inventory.rs
index 7514d76c0..5e4318e21 100644
--- a/src/tape/inventory.rs
+++ b/src/tape/inventory.rs
@@ -84,6 +84,8 @@ struct MediaStateEntry {
     location: Option<MediaLocation>,
     #[serde(skip_serializing_if = "Option::is_none")]
     status: Option<MediaStatus>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    bytes_used: Option<u64>,
 }
 
 /// Media Inventory
@@ -211,6 +213,7 @@ impl Inventory {
                 } else {
                     previous.status
                 },
+                bytes_used: previous.bytes_used,
             };
             self.map.insert(uuid, entry);
         } else {
@@ -218,6 +221,7 @@ impl Inventory {
                 id: media_id,
                 location: None,
                 status: None,
+                bytes_used: None,
             };
             self.map.insert(uuid, entry);
         }
@@ -720,6 +724,32 @@ impl Inventory {
         self.set_media_location(uuid, Some(MediaLocation::Offline))
     }
 
+    /// Lock database, reload database, set bytes used for media, store database
+    pub fn set_media_bytes_used(
+        &mut self,
+        uuid: &Uuid,
+        bytes_used: Option<u64>,
+    ) -> Result<(), Error> {
+        let _lock = self.lock()?;
+        self.map = self.load_media_db()?;
+        if let Some(entry) = self.map.get_mut(uuid) {
+            entry.bytes_used = bytes_used;
+            self.update_helpers();
+            self.replace_file()?;
+            Ok(())
+        } else {
+            bail!("no such media '{}'", uuid);
+        }
+    }
+
+    /// Returns bytes used of the given media, if set
+    pub fn get_media_bytes_used(&self, uuid: &Uuid) -> Option<u64> {
+        match self.map.get(uuid) {
+            Some(entry) => entry.bytes_used,
+            None => None,
+        }
+    }
+
     /// Update online status
     pub fn update_online_status(&mut self, online_map: &OnlineStatusMap) -> Result<(), Error> {
         let _lock = self.lock()?;
diff --git a/src/tape/media_pool.rs b/src/tape/media_pool.rs
index 8f2b0adda..1e8c739e7 100644
--- a/src/tape/media_pool.rs
+++ b/src/tape/media_pool.rs
@@ -212,8 +212,11 @@ impl MediaPool {
         }
 
         let (status, location) = self.compute_media_state(&media_id);
+        let bytes_used = self.inventory.get_media_bytes_used(uuid);
 
-        Ok(BackupMedia::with_media_id(media_id, location, status))
+        Ok(BackupMedia::with_media_id(
+            media_id, location, status, bytes_used,
+        ))
     }
 
     /// List all media associated with this pool
@@ -224,7 +227,8 @@ impl MediaPool {
             .into_iter()
             .map(|media_id| {
                 let (status, location) = self.compute_media_state(&media_id);
-                BackupMedia::with_media_id(media_id, location, status)
+                let bytes_used = self.inventory.get_media_bytes_used(&media_id.label.uuid);
+                BackupMedia::with_media_id(media_id, location, status, bytes_used)
             })
             .collect()
     }
@@ -238,6 +242,15 @@ impl MediaPool {
         Ok(())
     }
 
+    /// Update bytes used for media in inventory
+    pub fn set_media_bytes_used(
+        &mut self,
+        uuid: &Uuid,
+        bytes_used: Option<u64>,
+    ) -> Result<(), Error> {
+        self.inventory.set_media_bytes_used(uuid, bytes_used)
+    }
+
     /// Make sure the current media set is usable for writing
     ///
     /// If not, starts a new media set. Also creates a new
@@ -715,15 +728,23 @@ pub struct BackupMedia {
     location: MediaLocation,
     /// Media status
     status: MediaStatus,
+    /// Bytes used
+    bytes_used: Option<u64>,
 }
 
 impl BackupMedia {
     /// Creates a new instance
-    pub fn with_media_id(id: MediaId, location: MediaLocation, status: MediaStatus) -> Self {
+    pub fn with_media_id(
+        id: MediaId,
+        location: MediaLocation,
+        status: MediaStatus,
+        bytes_used: Option<u64>,
+    ) -> Self {
         Self {
             id,
             location,
             status,
+            bytes_used,
         }
     }
 
@@ -776,4 +797,9 @@ impl BackupMedia {
     pub fn label_text(&self) -> &str {
         &self.id.label.label_text
     }
+
+    /// Returns the bytes used, if set
+    pub fn bytes_used(&self) -> Option<u64> {
+        self.bytes_used
+    }
 }
diff --git a/src/tape/pool_writer/mod.rs b/src/tape/pool_writer/mod.rs
index 68e28714a..1df297b68 100644
--- a/src/tape/pool_writer/mod.rs
+++ b/src/tape/pool_writer/mod.rs
@@ -203,6 +203,14 @@ impl PoolWriter {
         if let Some(ref mut status) = self.status {
             status.drive.sync()?; // sync all data to the tape
             status.bytes_written_after_sync = 0; // reset bytes written
+
+            // not all drives support that
+            if let Ok(stats) = status.drive.get_volume_statistics() {
+                self.pool.set_media_bytes_used(
+                    &status.media_uuid,
+                    Some(stats.total_used_native_capacity),
+                )?;
+            }
         }
         self.catalog_set.lock().unwrap().commit()?; // then commit the catalog
         Ok(())
@@ -237,7 +245,13 @@ impl PoolWriter {
         );
 
         if let Some(PoolWriterState { mut drive, .. }) = self.status.take() {
-            if last_media_uuid.is_some() {
+            if let Some(uuid) = &last_media_uuid {
+                // not all drives support that
+                if let Ok(stats) = drive.get_volume_statistics() {
+                    self.pool
+                        .set_media_bytes_used(uuid, Some(stats.total_used_native_capacity))?;
+                }
+
                 task_log!(worker, "eject current media");
                 drive.eject_media()?;
             }
diff --git a/www/tape/TapeInventory.js b/www/tape/TapeInventory.js
index 47d19acc0..305134e3e 100644
--- a/www/tape/TapeInventory.js
+++ b/www/tape/TapeInventory.js
@@ -16,6 +16,7 @@ Ext.define('pbs-model-tapes', {
 	'seq-nr',
 	'status',
 	'uuid',
+	'bytes-used',
     ],
     idProperty: 'uuid',
     proxy: {
@@ -326,5 +327,11 @@ Ext.define('PBS.TapeManagement.TapeInventory', {
 	    flex: 1,
 	    hidden: true,
 	},
+	{
+	    text: gettext("Bytes Used"),
+	    dataIndex: 'bytes-used',
+	    flex: 1,
+	    renderer: Proxmox.Utils.render_size,
+	},
     ],
 });
-- 
2.39.2





More information about the pbs-devel mailing list