Compare commits

..

1 Commits

Author SHA1 Message Date
overtrue d33e2fdcf1 chore(madmin): remove dead trace structs, keep TraceType bitflag helper only
TraceInfo, TraceInfoLegacy, TraceHTTPStats, TraceCallStats, TraceRequestInfo,
TraceResponseInfo, StorageStats, and OSStats are unreferenced outside trace.rs.
Trim to TraceType + its bitflag operations which are actively used by
service_commands.rs and profile_admin.rs.

-139 lines (215 -> 76 lines)
2026-08-21 23:54:02 +08:00
4 changed files with 54 additions and 199 deletions
+18 -57
View File
@@ -585,12 +585,9 @@ impl VersionsHistogram {
} }
} }
/// Replication statistics for a single target. /// Replication statistics for a single target
/// #[derive(Debug, Default, Clone, Serialize, Deserialize)]
/// Renamed from `ReplicationStats`; serde field names are preserved pub struct ReplicationStats {
/// byte-identically to maintain wire compatibility with existing snapshots.
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ReplicationTargetUsage {
pub pending_size: u64, pub pending_size: u64,
pub replicated_size: u64, pub replicated_size: u64,
pub failed_size: u64, pub failed_size: u64,
@@ -603,7 +600,7 @@ pub struct ReplicationTargetUsage {
pub replicated_count: u64, pub replicated_count: u64,
} }
impl ReplicationTargetUsage { impl ReplicationStats {
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
let Self { let Self {
pending_size, pending_size,
@@ -639,7 +636,7 @@ impl ReplicationTargetUsage {
/// Replication statistics for all targets /// Replication statistics for all targets
#[derive(Debug, Default, Clone, Serialize, Deserialize)] #[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ReplicationAllStats { pub struct ReplicationAllStats {
pub targets: HashMap<String, ReplicationTargetUsage>, pub targets: HashMap<String, ReplicationStats>,
pub replica_size: u64, pub replica_size: u64,
pub replica_count: u64, pub replica_count: u64,
} }
@@ -652,7 +649,7 @@ impl ReplicationAllStats {
targets, targets,
} = self; } = self;
*replica_size == 0 && *replica_count == 0 && targets.values().all(ReplicationTargetUsage::is_empty) *replica_size == 0 && *replica_count == 0 && targets.values().all(ReplicationStats::is_empty)
} }
#[deprecated(note = "use is_empty instead")] #[deprecated(note = "use is_empty instead")]
@@ -2469,7 +2466,7 @@ mod tests {
#[test] #[test]
fn replication_stats_empty_checks_every_field() { fn replication_stats_empty_checks_every_field() {
type SetField = fn(&mut ReplicationTargetUsage); type SetField = fn(&mut ReplicationStats);
let cases: [(&str, SetField); 10] = [ let cases: [(&str, SetField); 10] = [
("pending_size", |stats| stats.pending_size = 1), ("pending_size", |stats| stats.pending_size = 1),
@@ -2484,9 +2481,9 @@ mod tests {
("replicated_count", |stats| stats.replicated_count = 1), ("replicated_count", |stats| stats.replicated_count = 1),
]; ];
assert!(ReplicationTargetUsage::default().is_empty()); assert!(ReplicationStats::default().is_empty());
for (field, set_nonzero) in cases { for (field, set_nonzero) in cases {
let mut stats = ReplicationTargetUsage::default(); let mut stats = ReplicationStats::default();
set_nonzero(&mut stats); set_nonzero(&mut stats);
assert!(!stats.is_empty(), "{field} must make replication stats non-empty"); assert!(!stats.is_empty(), "{field} must make replication stats non-empty");
} }
@@ -2517,17 +2514,17 @@ mod tests {
} }
let empty_targets = ReplicationAllStats { let empty_targets = ReplicationAllStats {
targets: HashMap::from([("arn:test:empty".to_string(), ReplicationTargetUsage::default())]), targets: HashMap::from([("arn:test:empty".to_string(), ReplicationStats::default())]),
..Default::default() ..Default::default()
}; };
assert!(empty_targets.is_empty(), "all-empty targets must keep aggregate stats empty"); assert!(empty_targets.is_empty(), "all-empty targets must keep aggregate stats empty");
let stats = ReplicationAllStats { let stats = ReplicationAllStats {
targets: HashMap::from([ targets: HashMap::from([
("arn:test:empty".to_string(), ReplicationTargetUsage::default()), ("arn:test:empty".to_string(), ReplicationStats::default()),
( (
"arn:test:non-empty".to_string(), "arn:test:non-empty".to_string(),
ReplicationTargetUsage { ReplicationStats {
pending_count: 1, pending_count: 1,
..Default::default() ..Default::default()
}, },
@@ -2568,7 +2565,7 @@ mod tests {
replication_stats: Some(ReplicationAllStats { replication_stats: Some(ReplicationAllStats {
targets: HashMap::from([( targets: HashMap::from([(
"arn:test:pending".to_string(), "arn:test:pending".to_string(),
ReplicationTargetUsage { ReplicationStats {
pending_count: 1, pending_count: 1,
..Default::default() ..Default::default()
}, },
@@ -2717,7 +2714,7 @@ mod tests {
targets: HashMap::from([ targets: HashMap::from([
( (
"arn:self-only".to_string(), "arn:self-only".to_string(),
ReplicationTargetUsage { ReplicationStats {
pending_size: 7, pending_size: 7,
pending_count: 1, pending_count: 1,
..Default::default() ..Default::default()
@@ -2725,7 +2722,7 @@ mod tests {
), ),
( (
"arn:shared".to_string(), "arn:shared".to_string(),
ReplicationTargetUsage { ReplicationStats {
failed_size: 3, failed_size: 3,
failed_count: 1, failed_count: 1,
missed_threshold_size: 2, missed_threshold_size: 2,
@@ -2744,7 +2741,7 @@ mod tests {
targets: HashMap::from([ targets: HashMap::from([
( (
"arn:shared".to_string(), "arn:shared".to_string(),
ReplicationTargetUsage { ReplicationStats {
failed_size: 5, failed_size: 5,
failed_count: 2, failed_count: 2,
after_threshold_size: 4, after_threshold_size: 4,
@@ -2754,7 +2751,7 @@ mod tests {
), ),
( (
"arn:other-only".to_string(), "arn:other-only".to_string(),
ReplicationTargetUsage { ReplicationStats {
replicated_size: 11, replicated_size: 11,
replicated_count: 3, replicated_count: 3,
..Default::default() ..Default::default()
@@ -2996,7 +2993,7 @@ mod tests {
fn replication_target_deserialization_preserves_large_historical_maps() { fn replication_target_deserialization_preserves_large_historical_maps() {
let mut stats = ReplicationAllStats::default(); let mut stats = ReplicationAllStats::default();
for index in 0..=1024 { for index in 0..=1024 {
stats.targets.insert(format!("target-{index}"), ReplicationTargetUsage::default()); stats.targets.insert(format!("target-{index}"), ReplicationStats::default());
} }
let encoded = rmp_serde::to_vec_named(&stats).expect("large replication target fixture should encode"); let encoded = rmp_serde::to_vec_named(&stats).expect("large replication target fixture should encode");
let decoded = rmp_serde::from_slice::<ReplicationAllStats>(&encoded) let decoded = rmp_serde::from_slice::<ReplicationAllStats>(&encoded)
@@ -3005,42 +3002,6 @@ mod tests {
assert_eq!(decoded.targets.len(), stats.targets.len()); assert_eq!(decoded.targets.len(), stats.targets.len());
} }
/// Round-trip test: encoding a [`ReplicationTargetUsage`] and decoding it back
/// must produce the exact same value. This guards against accidental serde
/// field-name drift during the `ReplicationStats` -> `ReplicationTargetUsage`
/// rename. Wire-level field names are the serialized Rust field identifiers,
/// which must remain byte-identical.
#[test]
fn replication_target_usage_rmp_round_trip() {
let original = ReplicationTargetUsage {
pending_size: 100,
replicated_size: 2_000,
failed_size: 50,
failed_count: 3,
pending_count: 7,
missed_threshold_size: 11,
after_threshold_size: 22,
missed_threshold_count: 1,
after_threshold_count: 2,
replicated_count: 99,
};
let buf = rmp_serde::to_vec_named(&original).expect("encode ReplicationTargetUsage to msgpack");
let decoded: ReplicationTargetUsage =
rmp_serde::from_slice(&buf).expect("decode ReplicationTargetUsage from msgpack");
assert_eq!(original, decoded, "round-trip through rmp must preserve every field");
// Also verify that encoding as an unnamed sequence and then decoding
// with named fields produces the correct mapping (this catches reordering).
let named_buf = rmp_serde::to_vec_named(&original).expect("re-encode for field-name pinning");
// Spot-check that known field names appear in the named encoding.
let named_str = String::from_utf8_lossy(&named_buf);
assert!(named_str.contains("pending_size"), "field 'pending_size' must survive the rename");
assert!(named_str.contains("replicated_size"), "field 'replicated_size' must survive the rename");
assert!(named_str.contains("missed_threshold_size"), "field 'missed_threshold_size' must survive the rename");
assert!(named_str.contains("after_threshold_count"), "field 'after_threshold_count' must survive the rename");
}
#[test] #[test]
fn checked_merge_rejects_noncanonical_histograms_without_mutation() { fn checked_merge_rejects_noncanonical_histograms_without_mutation() {
let mut entry = DataUsageEntry { let mut entry = DataUsageEntry {
+32 -138
View File
@@ -12,18 +12,16 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::{collections::HashMap, time::Duration};
use jiff::Timestamp;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::heal_commands::HealResultItem; /// Bitflag helper for service trace categories.
///
/// Each variant occupies a single bit so that a `TraceType` value can represent
/// an arbitrary combination of categories via bitwise OR.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)] #[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
pub struct TraceType(u64); pub struct TraceType(u64);
impl TraceType { impl TraceType {
// Define some constants
pub const OS: TraceType = TraceType(1 << 0); pub const OS: TraceType = TraceType(1 << 0);
pub const STORAGE: TraceType = TraceType(1 << 1); pub const STORAGE: TraceType = TraceType(1 << 1);
pub const S3: TraceType = TraceType(1 << 2); pub const S3: TraceType = TraceType(1 << 2);
@@ -40,15 +38,13 @@ impl TraceType {
pub const FTP: TraceType = TraceType(1 << 13); pub const FTP: TraceType = TraceType(1 << 13);
pub const ILM: TraceType = TraceType(1 << 14); pub const ILM: TraceType = TraceType(1 << 14);
// MetricsAll must be last. /// All trace categories combined. Must be updated when adding new variants.
pub const ALL: TraceType = TraceType((1 << 15) - 1); pub const ALL: TraceType = TraceType((1 << 15) - 1);
pub fn new(t: u64) -> Self { pub fn new(t: u64) -> Self {
Self(t) Self(t)
} }
}
impl TraceType {
pub fn contains(&self, x: &TraceType) -> bool { pub fn contains(&self, x: &TraceType) -> bool {
(self.0 & x.0) == x.0 (self.0 & x.0) == x.0
} }
@@ -76,140 +72,38 @@ impl TraceType {
} }
} }
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct TraceInfo {
#[serde(rename = "type")]
trace_type: u64,
#[serde(rename = "nodename")]
node_name: String,
#[serde(rename = "funcname")]
func_name: String,
#[serde(rename = "time")]
time: Timestamp,
#[serde(rename = "path")]
path: String,
#[serde(rename = "dur")]
duration: Duration,
#[serde(rename = "bytes", skip_serializing_if = "Option::is_none")]
bytes: Option<i64>,
#[serde(rename = "msg", skip_serializing_if = "Option::is_none")]
message: Option<String>,
#[serde(rename = "error", skip_serializing_if = "Option::is_none")]
error: Option<String>,
#[serde(rename = "custom", skip_serializing_if = "Option::is_none")]
custom: Option<HashMap<String, String>>,
#[serde(rename = "http", skip_serializing_if = "Option::is_none")]
http: Option<TraceHTTPStats>,
#[serde(rename = "healResult", skip_serializing_if = "Option::is_none")]
heal_result: Option<HealResultItem>,
}
impl TraceInfo {
pub fn mask(&self) -> u64 {
TraceType::new(self.trace_type).mask()
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct TraceInfoLegacy {
trace_info: TraceInfo,
#[serde(rename = "request")]
req_info: Option<TraceRequestInfo>,
#[serde(rename = "response")]
resp_info: Option<TraceResponseInfo>,
#[serde(rename = "stats")]
call_stats: Option<TraceCallStats>,
#[serde(rename = "storageStats")]
storage_stats: Option<StorageStats>,
#[serde(rename = "osStats")]
os_stats: Option<OSStats>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct StorageStats {
path: String,
duration: Duration,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct OSStats {
path: String,
duration: Duration,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct TraceHTTPStats {
req_info: TraceRequestInfo,
resp_info: TraceResponseInfo,
call_stats: TraceCallStats,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct TraceCallStats {
input_bytes: i32,
output_bytes: i32,
latency: Duration,
time_to_first_byte: Duration,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct TraceRequestInfo {
time: Timestamp,
proto: String,
method: String,
#[serde(skip_serializing_if = "Option::is_none")]
path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
raw_query: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
headers: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
body: Option<Vec<u8>>,
client: String,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct TraceResponseInfo {
time: Timestamp,
#[serde(skip_serializing_if = "Option::is_none")]
headers: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
body: Option<Vec<u8>>,
#[serde(skip_serializing_if = "Option::is_none")]
status_code: Option<i32>,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[test] #[test]
fn trace_timestamps_serialize_as_rfc3339_utc() { fn trace_type_contains_and_overlaps() {
let timestamp = Timestamp::constant(1_700_000_000, 123_456_000); let mut combined = TraceType::default();
let trace = TraceInfo { combined.merge(&TraceType::S3);
time: timestamp, combined.merge(&TraceType::HEALING);
http: Some(TraceHTTPStats {
req_info: TraceRequestInfo {
time: timestamp,
..Default::default()
},
resp_info: TraceResponseInfo {
time: timestamp,
..Default::default()
},
..Default::default()
}),
..Default::default()
};
let value = serde_json::to_value(trace).expect("trace should serialize"); assert!(combined.contains(&TraceType::S3));
assert_eq!(value["time"], "2023-11-14T22:13:20.123456Z"); assert!(combined.contains(&TraceType::HEALING));
assert_eq!(value["http"]["req_info"]["time"], "2023-11-14T22:13:20.123456Z"); assert!(!combined.contains(&TraceType::SCANNER));
assert_eq!(value["http"]["resp_info"]["time"], "2023-11-14T22:13:20.123456Z"); assert!(combined.overlaps(&TraceType::S3));
let trace: TraceInfo = serde_json::from_value(value).expect("trace should deserialize"); assert!(combined.overlaps(&TraceType::HEALING));
assert_eq!(trace.time, timestamp); assert!(!combined.overlaps(&TraceType::SCANNER));
let http = trace.http.expect("http trace should deserialize"); }
assert_eq!(http.req_info.time, timestamp);
assert_eq!(http.resp_info.time, timestamp); #[test]
fn trace_type_set_if() {
let mut tt = TraceType::default();
tt.set_if(true, &TraceType::OS);
tt.set_if(false, &TraceType::S3);
assert!(tt.contains(&TraceType::OS));
assert!(!tt.contains(&TraceType::S3));
}
#[test]
fn trace_type_single_type() {
assert!(TraceType::S3.single_type());
let mut combined = TraceType::S3;
combined.merge(&TraceType::HEALING);
assert!(!combined.single_type());
} }
} }
@@ -16,7 +16,7 @@ use super::persistence::DataUsageCacheLoadAttempt;
use super::*; use super::*;
use crate::storage_api::scanner_io::{HTTPRangeSpec, ObjectIO}; use crate::storage_api::scanner_io::{HTTPRangeSpec, ObjectIO};
use crate::{ScannerGetObjectReader, ScannerPutObjReader}; use crate::{ScannerGetObjectReader, ScannerPutObjReader};
use rustfs_data_usage::{ReplicationAllStats, ReplicationTargetUsage}; use rustfs_data_usage::{ReplicationAllStats, ReplicationStats};
use serde_json::Value; use serde_json::Value;
use std::io::Cursor; use std::io::Cursor;
use std::pin::Pin; use std::pin::Pin;
@@ -1636,7 +1636,7 @@ fn size_recursive_prunes_empty_and_preserves_threshold_replication_stats() {
replication_stats: Some(ReplicationAllStats { replication_stats: Some(ReplicationAllStats {
targets: HashMap::from([( targets: HashMap::from([(
"arn:test:threshold".to_string(), "arn:test:threshold".to_string(),
ReplicationTargetUsage { ReplicationStats {
after_threshold_count: 1, after_threshold_count: 1,
..Default::default() ..Default::default()
}, },
@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
use super::*; use super::*;
use rustfs_data_usage::{ReplicationAllStats, ReplicationTargetUsage}; use rustfs_data_usage::{ReplicationAllStats, ReplicationStats};
const TEST_PLAN_DIGEST: DataUsageScanPlanDigest = DataUsageScanPlanDigest([7; 32]); const TEST_PLAN_DIGEST: DataUsageScanPlanDigest = DataUsageScanPlanDigest([7; 32]);
@@ -271,7 +271,7 @@ fn completed_data_usage_info_flattens_nested_bucket_entries() {
replication_stats: Some(ReplicationAllStats { replication_stats: Some(ReplicationAllStats {
targets: HashMap::from([( targets: HashMap::from([(
"arn:target".to_string(), "arn:target".to_string(),
ReplicationTargetUsage { ReplicationStats {
replicated_size: 2048, replicated_size: 2048,
replicated_count: 2, replicated_count: 2,
..Default::default() ..Default::default()