use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::options::RetryJitter;
use crate::s3_uri::{S3Object, S3Prefix};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct UploadReport {
pub source_dir: String,
pub destination: S3Object,
pub files: usize,
#[serde(default)]
pub directories: usize,
#[serde(default = "default_include_catalog")]
pub include_catalog: bool,
pub uncompressed_bytes: u64,
pub zip_bytes: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct S3PrefixUploadReport {
pub source: S3Prefix,
pub destination: S3Object,
pub files: usize,
pub directories: usize,
#[serde(default = "default_include_catalog")]
pub include_catalog: bool,
pub entries: usize,
pub uncompressed_bytes: u64,
pub zip_bytes: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LocalZipReport {
pub source: String,
pub destination_zip: String,
pub files: usize,
pub directories: usize,
#[serde(default = "default_include_catalog")]
pub include_catalog: bool,
pub entries: usize,
pub uncompressed_bytes: u64,
pub zip_bytes: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ZipDryRunReport {
pub source: String,
pub destination: String,
pub files: usize,
pub directories: usize,
pub entries: usize,
pub uncompressed_bytes: u64,
pub include_catalog: bool,
}
fn default_include_catalog() -> bool {
true
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct SyncSummary {
pub zip_files: usize,
pub destination_objects: usize,
pub uploaded_new: usize,
pub uploaded_changed: usize,
pub skipped_unchanged: usize,
pub conditional_conflicts: usize,
pub deleted_extra: usize,
pub errors: usize,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct UnzipDryRunSummary {
pub zip_files: usize,
pub destination_objects: usize,
pub would_upload_new: usize,
pub would_upload_changed: usize,
pub skipped_unchanged: usize,
pub would_delete_extra: usize,
pub errors: usize,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SyncReport {
pub source: S3Object,
pub destination: S3Prefix,
pub summary: SyncSummary,
#[serde(skip_serializing_if = "Option::is_none")]
pub diagnostics: Option<SyncDiagnostics>,
pub operations: Vec<ObjectReport>,
}
impl SyncReport {
pub fn has_errors(&self) -> bool {
self.summary.errors > 0
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LocalZipToS3Report {
pub source_zip: String,
pub destination: S3Prefix,
pub summary: SyncSummary,
pub operations: Vec<ObjectReport>,
}
impl LocalZipToS3Report {
pub fn has_errors(&self) -> bool {
self.summary.errors > 0
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LocalUnzipReport {
pub source_zip: String,
pub destination_dir: String,
pub summary: SyncSummary,
#[serde(skip_serializing_if = "Option::is_none")]
pub diagnostics: Option<LocalUnzipDiagnostics>,
pub operations: Vec<ObjectReport>,
}
impl LocalUnzipReport {
pub fn has_errors(&self) -> bool {
self.summary.errors > 0
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct UnzipDryRunReport {
pub source_zip: String,
pub destination: String,
pub summary: UnzipDryRunSummary,
#[serde(skip_serializing_if = "Option::is_none")]
pub diagnostics: Option<DryRunDiagnostics>,
pub operations: Vec<DryRunObjectReport>,
}
impl UnzipDryRunReport {
pub fn has_errors(&self) -> bool {
self.summary.errors > 0
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SyncDiagnostics {
pub concurrency: usize,
pub put_concurrency: usize,
pub put_retry: PutRetryDiagnostics,
pub source_block_size: usize,
pub source_block_merge_gap: usize,
pub source_get_concurrency: usize,
pub source_window_capacity: usize,
pub source: SourceDiagnostics,
pub put: PutDiagnostics,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LocalUnzipDiagnostics {
pub concurrency: usize,
pub source_block_size: usize,
pub source_block_merge_gap: usize,
pub source_get_concurrency: usize,
pub source_window_capacity: usize,
pub source: SourceDiagnostics,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DryRunDiagnostics {
pub concurrency: usize,
pub source_block_size: usize,
pub source_block_merge_gap: usize,
pub source_get_concurrency: usize,
pub source_window_capacity: usize,
pub source: SourceDiagnostics,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SourceDiagnostics {
pub source_zip_bytes: u64,
pub planned_entries: u64,
pub planned_blocks: u64,
pub fetched_blocks: u64,
pub source_get_attempts: u64,
pub source_get_retries: u64,
pub source_get_request_errors: u64,
pub source_get_body_errors: u64,
pub source_get_short_body_errors: u64,
pub source_get_errors: u64,
pub planned_source_bytes: u64,
pub fetched_source_bytes: u64,
pub unique_source_bytes: u64,
pub source_amplification: f64,
pub block_hits: u64,
pub block_waits: u64,
pub block_releases: u64,
pub block_misses: u64,
pub block_refetches: u64,
pub active_gets_high_water: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PutRetryDiagnostics {
pub max_attempts: usize,
pub base_delay_ms: u64,
pub max_delay_ms: u64,
pub slowdown_base_delay_ms: u64,
pub slowdown_max_delay_ms: u64,
pub jitter: RetryJitter,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct PutDiagnostics {
pub failed_attempts: u64,
pub failures_by_error_code: BTreeMap<String, u64>,
pub retry_attempts: u64,
pub throttled_attempts: u64,
pub throttle_waits: u64,
pub throttle_wait_millis: u64,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum OperationStatus {
UploadedNew,
UploadedChanged,
SkippedUnchanged,
ConditionalConflict,
DeletedExtra,
Error,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DryRunOperationStatus {
WouldUploadNew,
WouldUploadChanged,
SkippedUnchanged,
WouldDeleteExtra,
Error,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ObjectReport {
pub status: OperationStatus,
pub key: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub zip_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub md5: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub destination_etag: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DryRunObjectReport {
pub status: DryRunOperationStatus,
pub key: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub zip_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub md5: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub destination_etag: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
#[cfg(test)]
pub(crate) fn summarize(report: &mut SyncReport) {
for operation in &report.operations {
summarize_operation(&mut report.summary, operation);
}
}
pub(crate) fn summarize_dry_run_operation(
summary: &mut UnzipDryRunSummary,
operation: &DryRunObjectReport,
) {
match operation.status {
DryRunOperationStatus::WouldUploadNew => summary.would_upload_new += 1,
DryRunOperationStatus::WouldUploadChanged => summary.would_upload_changed += 1,
DryRunOperationStatus::SkippedUnchanged => summary.skipped_unchanged += 1,
DryRunOperationStatus::WouldDeleteExtra => summary.would_delete_extra += 1,
DryRunOperationStatus::Error => summary.errors += 1,
}
}
pub(crate) fn summarize_operation(summary: &mut SyncSummary, operation: &ObjectReport) {
match operation.status {
OperationStatus::UploadedNew => summary.uploaded_new += 1,
OperationStatus::UploadedChanged => summary.uploaded_changed += 1,
OperationStatus::SkippedUnchanged => summary.skipped_unchanged += 1,
OperationStatus::ConditionalConflict => summary.conditional_conflicts += 1,
OperationStatus::DeletedExtra => summary.deleted_extra += 1,
OperationStatus::Error => summary.errors += 1,
}
}