mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-10 15:16:56 +00:00
feat: consolidate AI rules into unified AGENTS.md (#501)
- Merge all AI rules from .rules.md, .cursorrules, and CLAUDE.md into AGENTS.md - Add competitor keyword prohibition rules (minio, ceph, swift, etc.) - Simplify rules by removing overly detailed code examples - Integrate new development principles as highest priority - Remove old tool-specific rule files - Fix clippy warnings for format string improvements
This commit is contained in:
@@ -91,9 +91,9 @@ impl CheckpointManager {
|
||||
}
|
||||
}
|
||||
|
||||
let checkpoint_file = data_dir.join(format!("scanner_checkpoint_{}.json", node_id));
|
||||
let backup_file = data_dir.join(format!("scanner_checkpoint_{}.backup", node_id));
|
||||
let temp_file = data_dir.join(format!("scanner_checkpoint_{}.tmp", node_id));
|
||||
let checkpoint_file = data_dir.join(format!("scanner_checkpoint_{node_id}.json"));
|
||||
let backup_file = data_dir.join(format!("scanner_checkpoint_{node_id}.backup"));
|
||||
let temp_file = data_dir.join(format!("scanner_checkpoint_{node_id}.tmp"));
|
||||
|
||||
Self {
|
||||
checkpoint_file,
|
||||
@@ -116,21 +116,21 @@ impl CheckpointManager {
|
||||
let checkpoint_data = CheckpointData::new(progress.clone(), self.node_id.clone());
|
||||
|
||||
let json_data = serde_json::to_string_pretty(&checkpoint_data)
|
||||
.map_err(|e| Error::Serialization(format!("serialize checkpoint failed: {}", e)))?;
|
||||
.map_err(|e| Error::Serialization(format!("serialize checkpoint failed: {e}")))?;
|
||||
|
||||
tokio::fs::write(&self.temp_file, json_data)
|
||||
.await
|
||||
.map_err(|e| Error::IO(format!("write temp checkpoint file failed: {}", e)))?;
|
||||
.map_err(|e| Error::IO(format!("write temp checkpoint file failed: {e}")))?;
|
||||
|
||||
if self.checkpoint_file.exists() {
|
||||
tokio::fs::copy(&self.checkpoint_file, &self.backup_file)
|
||||
.await
|
||||
.map_err(|e| Error::IO(format!("backup checkpoint file failed: {}", e)))?;
|
||||
.map_err(|e| Error::IO(format!("backup checkpoint file failed: {e}")))?;
|
||||
}
|
||||
|
||||
tokio::fs::rename(&self.temp_file, &self.checkpoint_file)
|
||||
.await
|
||||
.map_err(|e| Error::IO(format!("replace checkpoint file failed: {}", e)))?;
|
||||
.map_err(|e| Error::IO(format!("replace checkpoint file failed: {e}")))?;
|
||||
|
||||
*self.last_save.write().await = now;
|
||||
|
||||
@@ -183,17 +183,17 @@ impl CheckpointManager {
|
||||
/// load checkpoint from file
|
||||
async fn load_checkpoint_from_file(&self, file_path: &Path) -> Result<ScanProgress> {
|
||||
if !file_path.exists() {
|
||||
return Err(Error::NotFound(format!("checkpoint file not exists: {:?}", file_path)));
|
||||
return Err(Error::NotFound(format!("checkpoint file not exists: {file_path:?}")));
|
||||
}
|
||||
|
||||
// read file content
|
||||
let content = tokio::fs::read_to_string(file_path)
|
||||
.await
|
||||
.map_err(|e| Error::IO(format!("read checkpoint file failed: {}", e)))?;
|
||||
.map_err(|e| Error::IO(format!("read checkpoint file failed: {e}")))?;
|
||||
|
||||
// deserialize
|
||||
let checkpoint_data: CheckpointData =
|
||||
serde_json::from_str(&content).map_err(|e| Error::Serialization(format!("deserialize checkpoint failed: {}", e)))?;
|
||||
serde_json::from_str(&content).map_err(|e| Error::Serialization(format!("deserialize checkpoint failed: {e}")))?;
|
||||
|
||||
// validate checkpoint data
|
||||
self.validate_checkpoint(&checkpoint_data)?;
|
||||
@@ -223,7 +223,7 @@ impl CheckpointManager {
|
||||
|
||||
// checkpoint is too old (more than 24 hours), may be data expired
|
||||
if checkpoint_age > Duration::from_secs(24 * 3600) {
|
||||
return Err(Error::InvalidCheckpoint(format!("checkpoint data is too old: {:?}", checkpoint_age)));
|
||||
return Err(Error::InvalidCheckpoint(format!("checkpoint data is too old: {checkpoint_age:?}")));
|
||||
}
|
||||
|
||||
// validate version compatibility
|
||||
@@ -245,21 +245,21 @@ impl CheckpointManager {
|
||||
if self.checkpoint_file.exists() {
|
||||
tokio::fs::remove_file(&self.checkpoint_file)
|
||||
.await
|
||||
.map_err(|e| Error::IO(format!("delete main checkpoint file failed: {}", e)))?;
|
||||
.map_err(|e| Error::IO(format!("delete main checkpoint file failed: {e}")))?;
|
||||
}
|
||||
|
||||
// delete backup file
|
||||
if self.backup_file.exists() {
|
||||
tokio::fs::remove_file(&self.backup_file)
|
||||
.await
|
||||
.map_err(|e| Error::IO(format!("delete backup checkpoint file failed: {}", e)))?;
|
||||
.map_err(|e| Error::IO(format!("delete backup checkpoint file failed: {e}")))?;
|
||||
}
|
||||
|
||||
// delete temp file
|
||||
if self.temp_file.exists() {
|
||||
tokio::fs::remove_file(&self.temp_file)
|
||||
.await
|
||||
.map_err(|e| Error::IO(format!("delete temp checkpoint file failed: {}", e)))?;
|
||||
.map_err(|e| Error::IO(format!("delete temp checkpoint file failed: {e}")))?;
|
||||
}
|
||||
|
||||
info!("cleaned up all checkpoint files");
|
||||
@@ -274,14 +274,14 @@ impl CheckpointManager {
|
||||
|
||||
let metadata = tokio::fs::metadata(&self.checkpoint_file)
|
||||
.await
|
||||
.map_err(|e| Error::IO(format!("get checkpoint file metadata failed: {}", e)))?;
|
||||
.map_err(|e| Error::IO(format!("get checkpoint file metadata failed: {e}")))?;
|
||||
|
||||
let content = tokio::fs::read_to_string(&self.checkpoint_file)
|
||||
.await
|
||||
.map_err(|e| Error::IO(format!("read checkpoint file failed: {}", e)))?;
|
||||
.map_err(|e| Error::IO(format!("read checkpoint file failed: {e}")))?;
|
||||
|
||||
let checkpoint_data: CheckpointData =
|
||||
serde_json::from_str(&content).map_err(|e| Error::Serialization(format!("deserialize checkpoint failed: {}", e)))?;
|
||||
serde_json::from_str(&content).map_err(|e| Error::Serialization(format!("deserialize checkpoint failed: {e}")))?;
|
||||
|
||||
Ok(Some(CheckpointInfo {
|
||||
file_size: metadata.len(),
|
||||
|
||||
@@ -2580,11 +2580,11 @@ mod tests {
|
||||
let temp_dir = std::path::PathBuf::from(test_base_dir);
|
||||
if temp_dir.exists() {
|
||||
if let Err(e) = fs::remove_dir_all(&temp_dir) {
|
||||
panic!("Failed to remove test directory: {}", e);
|
||||
panic!("Failed to remove test directory: {e}");
|
||||
}
|
||||
}
|
||||
if let Err(e) = fs::create_dir_all(&temp_dir) {
|
||||
panic!("Failed to create test directory: {}", e);
|
||||
panic!("Failed to create test directory: {e}");
|
||||
}
|
||||
|
||||
// create 4 disk dirs
|
||||
@@ -2597,7 +2597,7 @@ mod tests {
|
||||
|
||||
for disk_path in &disk_paths {
|
||||
if let Err(e) = fs::create_dir_all(disk_path) {
|
||||
panic!("Failed to create disk directory {:?}: {}", disk_path, e);
|
||||
panic!("Failed to create disk directory {disk_path:?}: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2768,13 +2768,13 @@ mod tests {
|
||||
// Try to create bucket, handle case where it might already exist
|
||||
match ecstore.make_bucket(bucket, &Default::default()).await {
|
||||
Ok(_) => {
|
||||
println!("Successfully created bucket: {}", bucket);
|
||||
println!("Successfully created bucket: {bucket}");
|
||||
}
|
||||
Err(rustfs_ecstore::error::StorageError::BucketExists(_)) => {
|
||||
println!("Bucket {} already exists, continuing with test", bucket);
|
||||
println!("Bucket {bucket} already exists, continuing with test");
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("Failed to create bucket {}: {}", bucket, e);
|
||||
panic!("Failed to create bucket {bucket}: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2833,14 +2833,13 @@ mod tests {
|
||||
if retry_count >= 3 {
|
||||
// If we still can't load after 3 retries, log and skip this verification
|
||||
println!(
|
||||
"Warning: Could not load persisted data after {} retries: {}. Skipping persistence verification.",
|
||||
retry_count, e
|
||||
"Warning: Could not load persisted data after {retry_count} retries: {e}. Skipping persistence verification."
|
||||
);
|
||||
println!("This is likely due to concurrent test execution and doesn't indicate a functional issue.");
|
||||
// Just continue with the rest of the test
|
||||
break DataUsageInfo::new(); // Use empty data to skip assertions
|
||||
}
|
||||
println!("Retry {} loading persisted data after error: {}", retry_count, e);
|
||||
println!("Retry {retry_count} loading persisted data after error: {e}");
|
||||
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,9 +121,9 @@ impl LocalStatsManager {
|
||||
}
|
||||
}
|
||||
|
||||
let stats_file = data_dir.join(format!("scanner_stats_{}.json", node_id));
|
||||
let backup_file = data_dir.join(format!("scanner_stats_{}.backup", node_id));
|
||||
let temp_file = data_dir.join(format!("scanner_stats_{}.tmp", node_id));
|
||||
let stats_file = data_dir.join(format!("scanner_stats_{node_id}.json"));
|
||||
let backup_file = data_dir.join(format!("scanner_stats_{node_id}.backup"));
|
||||
let temp_file = data_dir.join(format!("scanner_stats_{node_id}.tmp"));
|
||||
|
||||
Self {
|
||||
node_id: node_id.to_string(),
|
||||
@@ -172,10 +172,10 @@ impl LocalStatsManager {
|
||||
async fn load_stats_from_file(&self, file_path: &Path) -> Result<LocalScanStats> {
|
||||
let content = tokio::fs::read_to_string(file_path)
|
||||
.await
|
||||
.map_err(|e| Error::IO(format!("read stats file failed: {}", e)))?;
|
||||
.map_err(|e| Error::IO(format!("read stats file failed: {e}")))?;
|
||||
|
||||
let stats: LocalScanStats =
|
||||
serde_json::from_str(&content).map_err(|e| Error::Serialization(format!("deserialize stats data failed: {}", e)))?;
|
||||
serde_json::from_str(&content).map_err(|e| Error::Serialization(format!("deserialize stats data failed: {e}")))?;
|
||||
|
||||
Ok(stats)
|
||||
}
|
||||
@@ -194,24 +194,24 @@ impl LocalStatsManager {
|
||||
|
||||
// serialize
|
||||
let json_data = serde_json::to_string_pretty(&stats)
|
||||
.map_err(|e| Error::Serialization(format!("serialize stats data failed: {}", e)))?;
|
||||
.map_err(|e| Error::Serialization(format!("serialize stats data failed: {e}")))?;
|
||||
|
||||
// atomic write
|
||||
tokio::fs::write(&self.temp_file, json_data)
|
||||
.await
|
||||
.map_err(|e| Error::IO(format!("write temp stats file failed: {}", e)))?;
|
||||
.map_err(|e| Error::IO(format!("write temp stats file failed: {e}")))?;
|
||||
|
||||
// backup existing file
|
||||
if self.stats_file.exists() {
|
||||
tokio::fs::copy(&self.stats_file, &self.backup_file)
|
||||
.await
|
||||
.map_err(|e| Error::IO(format!("backup stats file failed: {}", e)))?;
|
||||
.map_err(|e| Error::IO(format!("backup stats file failed: {e}")))?;
|
||||
}
|
||||
|
||||
// atomic replace
|
||||
tokio::fs::rename(&self.temp_file, &self.stats_file)
|
||||
.await
|
||||
.map_err(|e| Error::IO(format!("replace stats file failed: {}", e)))?;
|
||||
.map_err(|e| Error::IO(format!("replace stats file failed: {e}")))?;
|
||||
|
||||
*self.last_save.write().await = now;
|
||||
|
||||
@@ -374,21 +374,21 @@ impl LocalStatsManager {
|
||||
if self.stats_file.exists() {
|
||||
tokio::fs::remove_file(&self.stats_file)
|
||||
.await
|
||||
.map_err(|e| Error::IO(format!("delete stats file failed: {}", e)))?;
|
||||
.map_err(|e| Error::IO(format!("delete stats file failed: {e}")))?;
|
||||
}
|
||||
|
||||
// delete backup file
|
||||
if self.backup_file.exists() {
|
||||
tokio::fs::remove_file(&self.backup_file)
|
||||
.await
|
||||
.map_err(|e| Error::IO(format!("delete backup stats file failed: {}", e)))?;
|
||||
.map_err(|e| Error::IO(format!("delete backup stats file failed: {e}")))?;
|
||||
}
|
||||
|
||||
// delete temp file
|
||||
if self.temp_file.exists() {
|
||||
tokio::fs::remove_file(&self.temp_file)
|
||||
.await
|
||||
.map_err(|e| Error::IO(format!("delete temp stats file failed: {}", e)))?;
|
||||
.map_err(|e| Error::IO(format!("delete temp stats file failed: {e}")))?;
|
||||
}
|
||||
|
||||
info!("cleanup all stats files");
|
||||
|
||||
@@ -1007,7 +1007,7 @@ impl NodeScanner {
|
||||
let object_size = self.estimate_object_size(&entry_path).await;
|
||||
|
||||
let entry = ScanResultEntry {
|
||||
object_path: format!("{}/{}", bucket_name, object_name),
|
||||
object_path: format!("{bucket_name}/{object_name}"),
|
||||
bucket_name: bucket_name.to_string(),
|
||||
object_size,
|
||||
is_healthy: true, // assume most objects are healthy
|
||||
@@ -1047,7 +1047,7 @@ impl NodeScanner {
|
||||
// simulate some scan results
|
||||
for i in 0..5 {
|
||||
let entry = ScanResultEntry {
|
||||
object_path: format!("/fallback-bucket/object_{}", i),
|
||||
object_path: format!("/fallback-bucket/object_{i}"),
|
||||
bucket_name: "fallback-bucket".to_string(),
|
||||
object_size: 1024 * (i + 1),
|
||||
is_healthy: true,
|
||||
|
||||
@@ -203,7 +203,7 @@ impl NodeClient {
|
||||
.get(url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| Error::Other(format!("HTTP request failed: {}", e)))?;
|
||||
.map_err(|e| Error::Other(format!("HTTP request failed: {e}")))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(Error::Other(format!("HTTP status error: {}", response.status())));
|
||||
@@ -212,7 +212,7 @@ impl NodeClient {
|
||||
let summary = response
|
||||
.json::<StatsSummary>()
|
||||
.await
|
||||
.map_err(|e| Error::Serialization(format!("deserialize stats data failed: {}", e)))?;
|
||||
.map_err(|e| Error::Serialization(format!("deserialize stats data failed: {e}")))?;
|
||||
|
||||
Ok(summary)
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ async fn test_endpoint_index_settings() -> anyhow::Result<()> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
|
||||
// create test disk paths
|
||||
let disk_paths: Vec<_> = (0..4).map(|i| temp_dir.path().join(format!("disk{}", i))).collect();
|
||||
let disk_paths: Vec<_> = (0..4).map(|i| temp_dir.path().join(format!("disk{i}"))).collect();
|
||||
|
||||
for path in &disk_paths {
|
||||
tokio::fs::create_dir_all(path).await?;
|
||||
@@ -60,9 +60,9 @@ async fn test_endpoint_index_settings() -> anyhow::Result<()> {
|
||||
|
||||
// validate all endpoint indexes are in valid range
|
||||
for (i, ep) in endpoints.iter().enumerate() {
|
||||
assert_eq!(ep.pool_idx, 0, "Endpoint {} pool_idx should be 0", i);
|
||||
assert_eq!(ep.set_idx, 0, "Endpoint {} set_idx should be 0", i);
|
||||
assert_eq!(ep.disk_idx, i as i32, "Endpoint {} disk_idx should be {}", i, i);
|
||||
assert_eq!(ep.pool_idx, 0, "Endpoint {i} pool_idx should be 0");
|
||||
assert_eq!(ep.set_idx, 0, "Endpoint {i} set_idx should be 0");
|
||||
assert_eq!(ep.disk_idx, i as i32, "Endpoint {i} disk_idx should be {i}");
|
||||
println!(
|
||||
"Endpoint {} indices are valid: pool={}, set={}, disk={}",
|
||||
i, ep.pool_idx, ep.set_idx, ep.disk_idx
|
||||
|
||||
@@ -270,15 +270,15 @@ async fn test_performance_impact_measurement() {
|
||||
};
|
||||
|
||||
println!("Performance impact measurement:");
|
||||
println!(" Baseline duration: {:?}", baseline_duration);
|
||||
println!(" With scanner duration: {:?}", with_scanner_duration);
|
||||
println!(" Overhead: {} ms", overhead_ms);
|
||||
println!(" Impact percentage: {:.2}%", impact_percentage);
|
||||
println!(" Baseline duration: {baseline_duration:?}");
|
||||
println!(" With scanner duration: {with_scanner_duration:?}");
|
||||
println!(" Overhead: {overhead_ms} ms");
|
||||
println!(" Impact percentage: {impact_percentage:.2}%");
|
||||
println!(" Meets optimization goals: {}", benchmark.meets_optimization_goals());
|
||||
|
||||
// Verify optimization target (business impact < 10%)
|
||||
// Note: In real environment this test may need longer time and real load
|
||||
assert!(impact_percentage < 50.0, "Performance impact too high: {:.2}%", impact_percentage);
|
||||
assert!(impact_percentage < 50.0, "Performance impact too high: {impact_percentage:.2}%");
|
||||
|
||||
io_monitor.stop().await;
|
||||
}
|
||||
@@ -308,7 +308,7 @@ async fn test_concurrent_scanner_operations() {
|
||||
tokio::spawn(async move {
|
||||
for _i in 0..5 {
|
||||
if let Err(e) = scanner.force_save_checkpoint().await {
|
||||
eprintln!("Checkpoint save failed: {}", e);
|
||||
eprintln!("Checkpoint save failed: {e}");
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
|
||||
@@ -319,14 +319,14 @@ async fn test_optimized_performance_characteristics() {
|
||||
|
||||
// Create several test objects
|
||||
for i in 0..10 {
|
||||
let object_name = format!("perf-object-{}", i);
|
||||
let object_name = format!("perf-object-{i}");
|
||||
let test_data = vec![b'A' + (i % 26) as u8; 1024 * (i + 1)]; // Variable size objects
|
||||
let mut put_reader = PutObjReader::from_vec(test_data);
|
||||
let object_opts = rustfs_ecstore::store_api::ObjectOptions::default();
|
||||
ecstore
|
||||
.put_object(bucket_name, &object_name, &mut put_reader, &object_opts)
|
||||
.await
|
||||
.unwrap_or_else(|_| panic!("Failed to create object {}", object_name));
|
||||
.unwrap_or_else(|_| panic!("Failed to create object {object_name}"));
|
||||
}
|
||||
|
||||
// Create optimized scanner
|
||||
@@ -340,7 +340,7 @@ async fn test_optimized_performance_characteristics() {
|
||||
let scan_result = scanner.scan_cycle().await;
|
||||
let scan_duration = start_time.elapsed();
|
||||
|
||||
println!("Optimized scan completed in: {:?}", scan_duration);
|
||||
println!("Optimized scan completed in: {scan_duration:?}");
|
||||
assert!(scan_result.is_ok(), "Performance scan should succeed");
|
||||
|
||||
// Verify the scan was reasonably fast (should be faster than old concurrent scanner)
|
||||
@@ -359,11 +359,11 @@ async fn test_optimized_performance_characteristics() {
|
||||
let _scan_result2 = scanner.scan_cycle().await;
|
||||
let scan_duration2 = start_time2.elapsed();
|
||||
|
||||
println!("Second optimized scan completed in: {:?}", scan_duration2);
|
||||
println!("Second optimized scan completed in: {scan_duration2:?}");
|
||||
|
||||
// Second scan should be similar or faster due to caching
|
||||
let performance_ratio = scan_duration2.as_millis() as f64 / scan_duration.as_millis() as f64;
|
||||
println!("Performance ratio (second/first): {:.2}", performance_ratio);
|
||||
println!("Performance ratio (second/first): {performance_ratio:.2}");
|
||||
|
||||
// Clean up
|
||||
let _ = std::fs::remove_dir_all(std::path::Path::new(TEST_DIR_PERF));
|
||||
@@ -404,7 +404,7 @@ async fn test_optimized_load_balancing_and_throttling() {
|
||||
];
|
||||
|
||||
for (expected_level, latency, qps, error_rate, connections) in load_scenarios {
|
||||
println!("Testing load scenario: {:?}", expected_level);
|
||||
println!("Testing load scenario: {expected_level:?}");
|
||||
|
||||
// Update business metrics to simulate load
|
||||
node_scanner
|
||||
@@ -416,7 +416,7 @@ async fn test_optimized_load_balancing_and_throttling() {
|
||||
|
||||
// Get current load level
|
||||
let current_level = io_monitor.get_business_load_level().await;
|
||||
println!("Detected load level: {:?}", current_level);
|
||||
println!("Detected load level: {current_level:?}");
|
||||
|
||||
// Get throttling decision
|
||||
let _current_metrics = io_monitor.get_current_metrics().await;
|
||||
|
||||
@@ -298,7 +298,7 @@ async fn test_scanner_performance_impact() {
|
||||
let throttle_stats = throttler.get_throttle_stats().await;
|
||||
|
||||
println!("Performance test results:");
|
||||
println!(" Load level: {:?}", load_level);
|
||||
println!(" Load level: {load_level:?}");
|
||||
println!(" Throttle decisions: {}", throttle_stats.total_decisions);
|
||||
println!(" Average delay: {:?}", throttle_stats.average_delay);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user