mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-06 05:17:42 +00:00
158 lines
6.4 KiB
Rust
158 lines
6.4 KiB
Rust
// Copyright 2024 RustFS Team
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
use crate::data_usage::BucketUsageInfo;
|
|
use crate::disk::RUSTFS_META_BUCKET;
|
|
use crate::error::{Error, Result};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::path::{Path, PathBuf};
|
|
use std::time::SystemTime;
|
|
use tokio::fs;
|
|
|
|
/// Directory used to store per-disk usage snapshots under the metadata bucket.
|
|
pub const DATA_USAGE_DIR: &str = "datausage";
|
|
/// Directory used to store incremental scan state files under the metadata bucket.
|
|
pub const DATA_USAGE_STATE_DIR: &str = "datausage/state";
|
|
/// Snapshot file format version, allows forward compatibility if the structure evolves.
|
|
pub const LOCAL_USAGE_SNAPSHOT_VERSION: u32 = 1;
|
|
|
|
/// Additional metadata describing which disk produced the snapshot.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
pub struct LocalUsageSnapshotMeta {
|
|
/// Disk UUID stored as a string for simpler serialization.
|
|
pub disk_id: String,
|
|
/// Pool index if this disk is bound to a specific pool.
|
|
pub pool_index: Option<usize>,
|
|
/// Set index if known.
|
|
pub set_index: Option<usize>,
|
|
/// Disk index inside the set if known.
|
|
pub disk_index: Option<usize>,
|
|
}
|
|
|
|
/// Usage snapshot produced by a single disk.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
pub struct LocalUsageSnapshot {
|
|
/// Format version recorded in the snapshot.
|
|
pub format_version: u32,
|
|
/// Snapshot metadata, including disk identity.
|
|
pub meta: LocalUsageSnapshotMeta,
|
|
/// Wall-clock timestamp when the snapshot was produced.
|
|
pub last_update: Option<SystemTime>,
|
|
/// Per-bucket usage statistics.
|
|
pub buckets_usage: HashMap<String, BucketUsageInfo>,
|
|
/// Cached bucket count to speed up aggregations.
|
|
pub buckets_count: u64,
|
|
/// Total objects counted on this disk.
|
|
pub objects_total_count: u64,
|
|
/// Total versions counted on this disk.
|
|
pub versions_total_count: u64,
|
|
/// Total delete markers counted on this disk.
|
|
pub delete_markers_total_count: u64,
|
|
/// Total bytes occupied by objects on this disk.
|
|
pub objects_total_size: u64,
|
|
}
|
|
|
|
impl LocalUsageSnapshot {
|
|
/// Create an empty snapshot with the default format version filled in.
|
|
pub fn new(meta: LocalUsageSnapshotMeta) -> Self {
|
|
Self {
|
|
format_version: LOCAL_USAGE_SNAPSHOT_VERSION,
|
|
meta,
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
/// Recalculate cached totals from the per-bucket map.
|
|
pub fn recompute_totals(&mut self) {
|
|
let mut buckets_count = 0u64;
|
|
let mut objects_total_count = 0u64;
|
|
let mut versions_total_count = 0u64;
|
|
let mut delete_markers_total_count = 0u64;
|
|
let mut objects_total_size = 0u64;
|
|
|
|
for usage in self.buckets_usage.values() {
|
|
buckets_count = buckets_count.saturating_add(1);
|
|
objects_total_count = objects_total_count.saturating_add(usage.objects_count);
|
|
versions_total_count = versions_total_count.saturating_add(usage.versions_count);
|
|
delete_markers_total_count = delete_markers_total_count.saturating_add(usage.delete_markers_count);
|
|
objects_total_size = objects_total_size.saturating_add(usage.size);
|
|
}
|
|
|
|
self.buckets_count = buckets_count;
|
|
self.objects_total_count = objects_total_count;
|
|
self.versions_total_count = versions_total_count;
|
|
self.delete_markers_total_count = delete_markers_total_count;
|
|
self.objects_total_size = objects_total_size;
|
|
}
|
|
}
|
|
|
|
/// Build the snapshot file name `<disk-id>.json`.
|
|
pub fn snapshot_file_name(disk_id: &str) -> String {
|
|
format!("{disk_id}.json")
|
|
}
|
|
|
|
/// Build the object path relative to `RUSTFS_META_BUCKET`, e.g. `datausage/<disk-id>.json`.
|
|
pub fn snapshot_object_path(disk_id: &str) -> String {
|
|
format!("{}/{}", DATA_USAGE_DIR, snapshot_file_name(disk_id))
|
|
}
|
|
|
|
/// Return the absolute path to `.rustfs.sys/datausage` on the given disk root.
|
|
pub fn data_usage_dir(root: &Path) -> PathBuf {
|
|
root.join(RUSTFS_META_BUCKET).join(DATA_USAGE_DIR)
|
|
}
|
|
|
|
/// Return the absolute path to `.rustfs.sys/datausage/state` on the given disk root.
|
|
pub fn data_usage_state_dir(root: &Path) -> PathBuf {
|
|
root.join(RUSTFS_META_BUCKET).join(DATA_USAGE_STATE_DIR)
|
|
}
|
|
|
|
/// Build the absolute path to the snapshot file for the provided disk ID.
|
|
pub fn snapshot_path(root: &Path, disk_id: &str) -> PathBuf {
|
|
data_usage_dir(root).join(snapshot_file_name(disk_id))
|
|
}
|
|
|
|
/// Read a snapshot from disk if it exists.
|
|
pub async fn read_snapshot(root: &Path, disk_id: &str) -> Result<Option<LocalUsageSnapshot>> {
|
|
let path = snapshot_path(root, disk_id);
|
|
match fs::read(&path).await {
|
|
Ok(content) => {
|
|
let snapshot = serde_json::from_slice::<LocalUsageSnapshot>(&content)
|
|
.map_err(|err| Error::other(format!("failed to deserialize snapshot {path:?}: {err}")))?;
|
|
Ok(Some(snapshot))
|
|
}
|
|
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
|
|
Err(err) => Err(Error::other(err)),
|
|
}
|
|
}
|
|
|
|
/// Persist a snapshot to disk, creating directories as needed and overwriting any existing file.
|
|
pub async fn write_snapshot(root: &Path, disk_id: &str, snapshot: &LocalUsageSnapshot) -> Result<()> {
|
|
let dir = data_usage_dir(root);
|
|
fs::create_dir_all(&dir).await.map_err(Error::other)?;
|
|
let path = dir.join(snapshot_file_name(disk_id));
|
|
let data = serde_json::to_vec_pretty(snapshot)
|
|
.map_err(|err| Error::other(format!("failed to serialize snapshot {path:?}: {err}")))?;
|
|
fs::write(&path, data).await.map_err(Error::other)
|
|
}
|
|
|
|
/// Ensure that the data usage directory structure exists on this disk root.
|
|
pub async fn ensure_data_usage_layout(root: &Path) -> Result<()> {
|
|
let usage_dir = data_usage_dir(root);
|
|
fs::create_dir_all(&usage_dir).await.map_err(Error::other)?;
|
|
let state_dir = data_usage_state_dir(root);
|
|
fs::create_dir_all(&state_dir).await.map_err(Error::other)?;
|
|
Ok(())
|
|
}
|