refactor(metrics): migrate scanner report timestamps to jiff (#5710)

* refactor(metrics): migrate scanner report timestamps to jiff

Co-Authored-By: heihutu <heihutu@gmail.com>

* refactor(madmin): migrate admin timestamps to jiff (#5712)

Co-authored-by: heihutu <heihutu@gmail.com>

* refactor(storage): migrate RPC DTO timestamps to jiff (#5713)

Co-authored-by: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
houseme
2026-08-05 02:35:38 +08:00
committed by GitHub
parent 3dabac4a09
commit 16c2928965
14 changed files with 458 additions and 115 deletions
+170 -57
View File
@@ -14,7 +14,7 @@
use std::collections::HashMap;
use chrono::{DateTime, Utc};
use jiff::Timestamp;
use serde::{Deserialize, Serialize};
use crate::health::MemInfo;
@@ -78,7 +78,7 @@ pub struct DiskIOStats {
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct DiskMetric {
#[serde(rename = "collected")]
pub collected_at: DateTime<Utc>,
pub collected_at: Timestamp,
#[serde(rename = "n_disks")]
pub n_disks: usize,
#[serde(rename = "offline")]
@@ -542,15 +542,15 @@ impl ScannerLifecycleTransitionSnapshot {
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ScannerMetrics {
#[serde(rename = "collected")]
pub collected_at: DateTime<Utc>,
pub collected_at: Timestamp,
#[serde(rename = "current_cycle")]
pub current_cycle: u64,
#[serde(rename = "current_cycle_active", default, skip_serializing_if = "Option::is_none")]
pub current_cycle_active: Option<bool>,
#[serde(rename = "current_started")]
pub current_started: DateTime<Utc>,
pub current_started: Timestamp,
#[serde(rename = "cycle_complete_times")]
pub cycles_completed_at: Vec<DateTime<Utc>>,
pub cycles_completed_at: Vec<Timestamp>,
#[serde(rename = "ongoing_buckets")]
pub ongoing_buckets: usize,
#[serde(rename = "active_scan_paths", default)]
@@ -1011,7 +1011,7 @@ impl Metrics {
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct RPCMetrics {
#[serde(rename = "collectedAt")]
pub collected_at: DateTime<Utc>,
pub collected_at: Timestamp,
pub connected: i32,
@@ -1041,7 +1041,7 @@ pub struct RPCMetrics {
pub out_queue: i32,
#[serde(rename = "lastPongTime")]
pub last_pong_time: DateTime<Utc>,
pub last_pong_time: Timestamp,
#[serde(rename = "lastPingMS")]
pub last_ping_ms: f64,
@@ -1050,7 +1050,7 @@ pub struct RPCMetrics {
pub max_ping_dur_ms: f64, // Maximum across all merged entries.
#[serde(rename = "lastConnectTime")]
pub last_connect_time: DateTime<Utc>,
pub last_connect_time: Timestamp,
#[serde(rename = "byDestination", skip_serializing_if = "Option::is_none")]
pub by_destination: Option<HashMap<String, RPCMetrics>>,
@@ -1125,7 +1125,7 @@ pub struct CPUMetrics {}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct NetMetrics {
#[serde(rename = "collected")]
pub collected_at: DateTime<Utc>,
pub collected_at: Timestamp,
#[serde(rename = "interfaceName")]
pub interface_name: String,
#[serde(rename = "netstats")]
@@ -1214,7 +1214,7 @@ pub struct NetDevLine {
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct MemMetrics {
#[serde(rename = "collected")]
pub collected_at: DateTime<Utc>,
pub collected_at: Timestamp,
#[serde(rename = "memInfo")]
pub info: MemInfo,
}
@@ -1222,13 +1222,13 @@ pub struct MemMetrics {
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct SiteResyncMetrics {
#[serde(rename = "collected")]
pub collected_at: DateTime<Utc>,
pub collected_at: Timestamp,
#[serde(rename = "resyncStatus", skip_serializing_if = "Option::is_none")]
pub resync_status: Option<String>,
#[serde(rename = "startTime")]
pub start_time: DateTime<Utc>,
pub start_time: Timestamp,
#[serde(rename = "lastUpdate")]
pub last_update: DateTime<Utc>,
pub last_update: Timestamp,
#[serde(rename = "numBuckets")]
pub num_buckets: i64,
#[serde(rename = "resyncID")]
@@ -1262,7 +1262,7 @@ impl SiteResyncMetrics {
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct BatchJobMetrics {
#[serde(rename = "collected")]
pub collected_at: DateTime<Utc>,
pub collected_at: Timestamp,
#[serde(rename = "Jobs")]
pub jobs: HashMap<String, JobMetric>,
}
@@ -1290,9 +1290,9 @@ pub struct JobMetric {
#[serde(rename = "jobType")]
pub job_type: String,
#[serde(rename = "startTime")]
pub start_time: DateTime<Utc>,
pub start_time: Timestamp,
#[serde(rename = "lastUpdate")]
pub last_update: DateTime<Utc>,
pub last_update: Timestamp,
#[serde(rename = "retryAttempts")]
pub retry_attempts: i32,
pub complete: bool,
@@ -1385,7 +1385,7 @@ impl RealtimeMetrics {
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct OsMetrics {
#[serde(rename = "collected")]
pub collected_at: DateTime<Utc>,
pub collected_at: Timestamp,
#[serde(rename = "life_time_ops")]
pub life_time_ops: HashMap<String, u64>,
#[serde(rename = "last_minute")]
@@ -1417,6 +1417,119 @@ pub struct Operations {
#[cfg(test)]
mod tests {
use super::*;
use jiff::SignedDuration;
fn fixed_timestamp() -> Timestamp {
Timestamp::constant(1_700_000_000, 123_456_000)
}
#[test]
fn admin_metrics_timestamps_serialize_as_rfc3339_utc() {
let timestamp = fixed_timestamp();
let disk = serde_json::to_value(DiskMetric {
collected_at: timestamp,
..Default::default()
})
.expect("disk metrics should serialize");
assert_eq!(disk["collected"], "2023-11-14T22:13:20.123456Z");
let disk: DiskMetric = serde_json::from_value(disk).expect("disk metrics should deserialize");
assert_eq!(disk.collected_at, timestamp);
let scanner = serde_json::to_value(ScannerMetrics {
collected_at: timestamp,
current_started: timestamp,
cycles_completed_at: vec![timestamp],
..Default::default()
})
.expect("scanner metrics should serialize");
assert_eq!(scanner["collected"], "2023-11-14T22:13:20.123456Z");
assert_eq!(scanner["current_started"], "2023-11-14T22:13:20.123456Z");
assert_eq!(scanner["cycle_complete_times"][0], "2023-11-14T22:13:20.123456Z");
let scanner: ScannerMetrics = serde_json::from_value(scanner).expect("scanner metrics should deserialize");
assert_eq!(scanner.collected_at, timestamp);
assert_eq!(scanner.current_started, timestamp);
assert_eq!(scanner.cycles_completed_at, vec![timestamp]);
let rpc = serde_json::to_value(RPCMetrics {
collected_at: timestamp,
last_pong_time: timestamp,
last_connect_time: timestamp,
..Default::default()
})
.expect("rpc metrics should serialize");
assert_eq!(rpc["collectedAt"], "2023-11-14T22:13:20.123456Z");
assert_eq!(rpc["lastPongTime"], "2023-11-14T22:13:20.123456Z");
assert_eq!(rpc["lastConnectTime"], "2023-11-14T22:13:20.123456Z");
let rpc: RPCMetrics = serde_json::from_value(rpc).expect("rpc metrics should deserialize");
assert_eq!(rpc.collected_at, timestamp);
assert_eq!(rpc.last_pong_time, timestamp);
assert_eq!(rpc.last_connect_time, timestamp);
let batch = serde_json::to_value(BatchJobMetrics {
collected_at: timestamp,
jobs: HashMap::from([(
"job-a".to_string(),
JobMetric {
job_id: "job-a".to_string(),
start_time: timestamp,
last_update: timestamp,
..Default::default()
},
)]),
})
.expect("batch metrics should serialize");
assert_eq!(batch["collected"], "2023-11-14T22:13:20.123456Z");
assert_eq!(batch["Jobs"]["job-a"]["startTime"], "2023-11-14T22:13:20.123456Z");
assert_eq!(batch["Jobs"]["job-a"]["lastUpdate"], "2023-11-14T22:13:20.123456Z");
let batch: BatchJobMetrics = serde_json::from_value(batch).expect("batch metrics should deserialize");
assert_eq!(batch.collected_at, timestamp);
let job = batch.jobs.get("job-a").expect("job should deserialize");
assert_eq!(job.start_time, timestamp);
assert_eq!(job.last_update, timestamp);
let net = serde_json::to_value(NetMetrics {
collected_at: timestamp,
..Default::default()
})
.expect("net metrics should serialize");
assert_eq!(net["collected"], "2023-11-14T22:13:20.123456Z");
let net: NetMetrics = serde_json::from_value(net).expect("net metrics should deserialize");
assert_eq!(net.collected_at, timestamp);
let mem = serde_json::to_value(MemMetrics {
collected_at: timestamp,
..Default::default()
})
.expect("mem metrics should serialize");
assert_eq!(mem["collected"], "2023-11-14T22:13:20.123456Z");
let mem: MemMetrics = serde_json::from_value(mem).expect("mem metrics should deserialize");
assert_eq!(mem.collected_at, timestamp);
let site_resync = serde_json::to_value(SiteResyncMetrics {
collected_at: timestamp,
start_time: timestamp,
last_update: timestamp,
..Default::default()
})
.expect("site resync metrics should serialize");
assert_eq!(site_resync["collected"], "2023-11-14T22:13:20.123456Z");
assert_eq!(site_resync["startTime"], "2023-11-14T22:13:20.123456Z");
assert_eq!(site_resync["lastUpdate"], "2023-11-14T22:13:20.123456Z");
let site_resync: SiteResyncMetrics = serde_json::from_value(site_resync).expect("site resync metrics should deserialize");
assert_eq!(site_resync.collected_at, timestamp);
assert_eq!(site_resync.start_time, timestamp);
assert_eq!(site_resync.last_update, timestamp);
let os = serde_json::to_value(OsMetrics {
collected_at: timestamp,
..Default::default()
})
.expect("os metrics should serialize");
assert_eq!(os["collected"], "2023-11-14T22:13:20.123456Z");
let os: OsMetrics = serde_json::from_value(os).expect("os metrics should deserialize");
assert_eq!(os.collected_at, timestamp);
}
#[test]
fn scanner_metrics_serializes_cycle_active_presence() {
@@ -1439,9 +1552,9 @@ mod tests {
#[test]
fn scanner_metrics_merge_prefers_an_active_first_cycle() {
let collected_at = Utc::now();
let idle_started = collected_at - chrono::Duration::hours(1);
let active_started = collected_at - chrono::Duration::seconds(5);
let collected_at = Timestamp::now();
let idle_started = collected_at - SignedDuration::from_hours(1);
let active_started = collected_at - SignedDuration::from_secs(5);
let mut scanner = ScannerMetrics {
collected_at,
current_cycle: 0,
@@ -1451,7 +1564,7 @@ mod tests {
};
scanner.merge(&ScannerMetrics {
collected_at: collected_at + chrono::Duration::seconds(1),
collected_at: collected_at + SignedDuration::from_secs(1),
current_cycle: 0,
current_cycle_active: Some(true),
current_started: active_started,
@@ -1481,14 +1594,14 @@ mod tests {
#[test]
fn scanner_metrics_merge_preserves_legacy_nonzero_active_signal() {
let collected_at = Utc::now();
let collected_at = Timestamp::now();
let mut scanner = ScannerMetrics {
collected_at,
..Default::default()
};
scanner.merge(&ScannerMetrics {
collected_at: collected_at + chrono::Duration::seconds(1),
collected_at: collected_at + SignedDuration::from_secs(1),
current_cycle: 7,
..Default::default()
});
@@ -1517,7 +1630,7 @@ mod tests {
#[test]
fn scanner_metrics_merge_preserves_explicit_inactive_nonzero_cycle() {
let collected_at = Utc::now();
let collected_at = Timestamp::now();
let mut scanner = ScannerMetrics::default();
scanner.merge(&ScannerMetrics {
@@ -1533,18 +1646,18 @@ mod tests {
#[test]
fn scanner_metrics_merge_cycle_active_is_order_independent() {
let collected_at = Utc::now();
let collected_at = Timestamp::now();
let legacy_active = ScannerMetrics {
collected_at,
current_cycle: 7,
current_started: collected_at - chrono::Duration::seconds(10),
current_started: collected_at - SignedDuration::from_secs(10),
..Default::default()
};
let explicit_idle = ScannerMetrics {
collected_at,
current_cycle: 0,
current_cycle_active: Some(false),
current_started: collected_at - chrono::Duration::hours(1),
current_started: collected_at - SignedDuration::from_hours(1),
..Default::default()
};
@@ -1575,21 +1688,21 @@ mod tests {
#[test]
fn scanner_metrics_merge_cycle_authority_is_order_independent() {
let collected_at = Utc::now();
let completion = collected_at - chrono::Duration::minutes(1);
let collected_at = Timestamp::now();
let completion = collected_at - SignedDuration::from_mins(1);
let earlier_active = ScannerMetrics {
collected_at,
current_cycle: 7,
current_cycle_active: Some(true),
current_started: collected_at - chrono::Duration::seconds(10),
current_started: collected_at - SignedDuration::from_secs(10),
cycles_completed_at: vec![completion],
..Default::default()
};
let later_active = ScannerMetrics {
collected_at: collected_at + chrono::Duration::seconds(1),
collected_at: collected_at + SignedDuration::from_secs(1),
current_cycle: 7,
current_cycle_active: Some(true),
current_started: collected_at - chrono::Duration::seconds(5),
current_started: collected_at - SignedDuration::from_secs(5),
cycles_completed_at: vec![completion],
..Default::default()
};
@@ -1607,13 +1720,13 @@ mod tests {
let stale_idle = ScannerMetrics {
collected_at,
current_cycle_active: Some(false),
current_started: collected_at - chrono::Duration::hours(1),
current_started: collected_at - SignedDuration::from_hours(1),
..Default::default()
};
let completed_idle = ScannerMetrics {
collected_at: collected_at + chrono::Duration::seconds(1),
collected_at: collected_at + SignedDuration::from_secs(1),
current_cycle_active: Some(false),
current_started: collected_at - chrono::Duration::seconds(5),
current_started: collected_at - SignedDuration::from_secs(5),
cycles_completed_at: vec![completion],
..Default::default()
};
@@ -1631,29 +1744,29 @@ mod tests {
#[test]
fn scanner_metrics_merge_cycle_authority_is_associative() {
let collected_at = Utc::now();
let older_completion = collected_at - chrono::Duration::minutes(3);
let last_completion = collected_at - chrono::Duration::minutes(1);
let collected_at = Timestamp::now();
let older_completion = collected_at - SignedDuration::from_mins(3);
let last_completion = collected_at - SignedDuration::from_mins(1);
let cycle_seven = ScannerMetrics {
collected_at,
current_cycle: 7,
current_cycle_active: Some(true),
current_started: collected_at - chrono::Duration::seconds(10),
current_started: collected_at - SignedDuration::from_secs(10),
cycles_completed_at: vec![older_completion, last_completion],
..Default::default()
};
let cycle_eight = ScannerMetrics {
collected_at: collected_at + chrono::Duration::seconds(1),
collected_at: collected_at + SignedDuration::from_secs(1),
current_cycle: 8,
current_cycle_active: Some(true),
current_started: collected_at - chrono::Duration::seconds(5),
current_started: collected_at - SignedDuration::from_secs(5),
cycles_completed_at: vec![last_completion],
..Default::default()
};
let newer_idle = ScannerMetrics {
collected_at: collected_at + chrono::Duration::hours(1),
collected_at: collected_at + SignedDuration::from_hours(1),
current_cycle_active: Some(false),
current_started: collected_at - chrono::Duration::hours(1),
current_started: collected_at - SignedDuration::from_hours(1),
..Default::default()
};
@@ -1694,7 +1807,7 @@ mod tests {
#[test]
fn scanner_metrics_merge_aggregates_partial_cycles_by_source() {
let collected_at = Utc::now();
let collected_at = Timestamp::now();
let mut scanner = ScannerMetrics {
collected_at,
last_cycle_partial_source: "usage".to_string(),
@@ -1717,7 +1830,7 @@ mod tests {
};
scanner.merge(&ScannerMetrics {
collected_at: collected_at + chrono::Duration::seconds(1),
collected_at: collected_at + SignedDuration::from_secs(1),
last_cycle_partial_source: "lifecycle".to_string(),
last_cycle_partial_source_code: 2,
pacing_pressure: ScannerPacingPressureSnapshot {
@@ -1769,7 +1882,7 @@ mod tests {
#[test]
fn scanner_metrics_merge_preserves_pause_pressure_without_duration() {
let collected_at = Utc::now();
let collected_at = Timestamp::now();
let mut scanner = ScannerMetrics {
collected_at,
pacing_pressure: ScannerPacingPressureSnapshot {
@@ -1780,7 +1893,7 @@ mod tests {
};
scanner.merge(&ScannerMetrics {
collected_at: collected_at + chrono::Duration::seconds(1),
collected_at: collected_at + SignedDuration::from_secs(1),
pacing_pressure: ScannerPacingPressureSnapshot::default(),
..Default::default()
});
@@ -1805,7 +1918,7 @@ mod tests {
#[test]
fn scanner_metrics_merge_aggregates_lifecycle_transition_status() {
let collected_at = Utc::now();
let collected_at = Timestamp::now();
let mut scanner = ScannerMetrics {
collected_at,
current_cycle_lifecycle_expiry_actions: 2,
@@ -1843,7 +1956,7 @@ mod tests {
};
scanner.merge(&ScannerMetrics {
collected_at: collected_at + chrono::Duration::seconds(1),
collected_at: collected_at + SignedDuration::from_secs(1),
current_cycle_lifecycle_expiry_actions: 11,
current_cycle_lifecycle_transition_actions: 13,
last_cycle_lifecycle_expiry_actions: 17,
@@ -1909,7 +2022,7 @@ mod tests {
#[test]
fn scanner_metrics_merge_aggregates_maintenance_control_status() {
let collected_at = Utc::now();
let collected_at = Timestamp::now();
let mut scanner = ScannerMetrics {
collected_at,
maintenance_control: ScannerMaintenanceControlSnapshot {
@@ -1930,7 +2043,7 @@ mod tests {
};
scanner.merge(&ScannerMetrics {
collected_at: collected_at + chrono::Duration::seconds(1),
collected_at: collected_at + SignedDuration::from_secs(1),
maintenance_control: ScannerMaintenanceControlSnapshot {
primary_control: "blocked_source".to_string(),
sources: vec![
@@ -1990,7 +2103,7 @@ mod tests {
#[test]
fn scanner_metrics_merge_aggregates_replication_repair_status() {
let collected_at = Utc::now();
let collected_at = Timestamp::now();
let mut scanner = ScannerMetrics {
collected_at,
replication_repair: vec![ScannerReplicationRepairSnapshot {
@@ -2005,7 +2118,7 @@ mod tests {
};
scanner.merge(&ScannerMetrics {
collected_at: collected_at + chrono::Duration::seconds(1),
collected_at: collected_at + SignedDuration::from_secs(1),
replication_repair: vec![
ScannerReplicationRepairSnapshot {
source: "bucket_replication".to_string(),
@@ -2066,7 +2179,7 @@ mod tests {
#[test]
fn scanner_metrics_merge_preserves_replication_repair_metadata_from_newer_nodes() {
let collected_at = Utc::now();
let collected_at = Timestamp::now();
let mut scanner = ScannerMetrics {
collected_at,
replication_repair: vec![ScannerReplicationRepairSnapshot {
@@ -2091,7 +2204,7 @@ mod tests {
};
scanner.merge(&ScannerMetrics {
collected_at: collected_at + chrono::Duration::seconds(1),
collected_at: collected_at + SignedDuration::from_secs(1),
replication_repair: vec![ScannerReplicationRepairSnapshot {
source: "bucket_replication".to_string(),
kind: "object".to_string(),
@@ -2149,7 +2262,7 @@ mod tests {
#[test]
fn scanner_metrics_merge_preserves_distributed_status_fields() {
let collected_at = Utc::now();
let collected_at = Timestamp::now();
let mut scanner = ScannerMetrics {
collected_at,
active_scan_paths: 1,
@@ -2223,7 +2336,7 @@ mod tests {
};
scanner.merge(&ScannerMetrics {
collected_at: collected_at + chrono::Duration::seconds(1),
collected_at: collected_at + SignedDuration::from_secs(1),
active_scan_paths: 2,
oldest_active_path_age_seconds: 45,
active_paths: vec!["node-b/disk-b/bucket-b".to_string()],
+39 -4
View File
@@ -14,7 +14,7 @@
use std::{collections::HashMap, time::Duration};
use chrono::{DateTime, Utc};
use jiff::Timestamp;
use serde::{Deserialize, Serialize};
use crate::heal_commands::HealResultItem;
@@ -85,7 +85,7 @@ pub struct TraceInfo {
#[serde(rename = "funcname")]
func_name: String,
#[serde(rename = "time")]
time: DateTime<Utc>,
time: Timestamp,
#[serde(rename = "path")]
path: String,
#[serde(rename = "dur")]
@@ -154,7 +154,7 @@ pub struct TraceCallStats {
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct TraceRequestInfo {
time: DateTime<Utc>,
time: Timestamp,
proto: String,
method: String,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -170,7 +170,7 @@ pub struct TraceRequestInfo {
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct TraceResponseInfo {
time: DateTime<Utc>,
time: Timestamp,
#[serde(skip_serializing_if = "Option::is_none")]
headers: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -178,3 +178,38 @@ pub struct TraceResponseInfo {
#[serde(skip_serializing_if = "Option::is_none")]
status_code: Option<i32>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn trace_timestamps_serialize_as_rfc3339_utc() {
let timestamp = Timestamp::constant(1_700_000_000, 123_456_000);
let trace = TraceInfo {
time: timestamp,
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_eq!(value["time"], "2023-11-14T22:13:20.123456Z");
assert_eq!(value["http"]["req_info"]["time"], "2023-11-14T22:13:20.123456Z");
assert_eq!(value["http"]["resp_info"]["time"], "2023-11-14T22:13:20.123456Z");
let trace: TraceInfo = serde_json::from_value(value).expect("trace should deserialize");
assert_eq!(trace.time, timestamp);
let http = trace.http.expect("http trace should deserialize");
assert_eq!(http.req_info.time, timestamp);
assert_eq!(http.resp_info.time, timestamp);
}
}