20260501 修复CurrentRepoIndex完整性并优化replay guard
This commit is contained in:
@@ -327,12 +327,20 @@ pub fn process_manifest_publication_point_fresh_after_repo_sync(
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct FreshPublicationPointTimingBreakdown {
|
||||
pub current_index_lock_ms: u64,
|
||||
pub manifest_load_ms: u64,
|
||||
pub manifest_index_lookup_ms: u64,
|
||||
pub manifest_blob_load_ms: u64,
|
||||
pub manifest_decode_ms: u64,
|
||||
pub replay_guard_ms: u64,
|
||||
pub replay_meta_hit: bool,
|
||||
pub replay_meta_miss: bool,
|
||||
pub manifest_entries_ms: u64,
|
||||
pub pack_files_ms: u64,
|
||||
pub pack_files_index_lookup_ms: u64,
|
||||
pub pack_files_blob_load_ms: u64,
|
||||
pub ee_path_validate_ms: u64,
|
||||
pub manifest_file_count: usize,
|
||||
}
|
||||
|
||||
pub fn process_manifest_publication_point_fresh_after_repo_sync_with_timing(
|
||||
@@ -620,7 +628,9 @@ pub(crate) fn try_build_fresh_publication_point_with_timing(
|
||||
ManifestFreshError,
|
||||
> {
|
||||
let mut timing = FreshPublicationPointTimingBreakdown::default();
|
||||
let current_index_lock_started = std::time::Instant::now();
|
||||
let current_index_guard = current_repo_index.and_then(|handle| handle.lock().ok());
|
||||
timing.current_index_lock_ms = current_index_lock_started.elapsed().as_millis() as u64;
|
||||
|
||||
if !rsync_uri_is_under_publication_point(manifest_rsync_uri, publication_point_rsync_uri) {
|
||||
return Err(ManifestFreshError::ManifestOutsidePublicationPoint {
|
||||
@@ -631,11 +641,14 @@ pub(crate) fn try_build_fresh_publication_point_with_timing(
|
||||
|
||||
let manifest_load_started = std::time::Instant::now();
|
||||
let manifest_bytes = if let Some(index) = current_index_guard.as_ref() {
|
||||
let manifest_lookup_started = std::time::Instant::now();
|
||||
let current = index.get_by_uri(manifest_rsync_uri).ok_or_else(|| {
|
||||
ManifestFreshError::MissingManifest {
|
||||
manifest_rsync_uri: manifest_rsync_uri.to_string(),
|
||||
}
|
||||
})?;
|
||||
timing.manifest_index_lookup_ms = manifest_lookup_started.elapsed().as_millis() as u64;
|
||||
let manifest_blob_load_started = std::time::Instant::now();
|
||||
store
|
||||
.get_blob_bytes(¤t.current_hash_hex)
|
||||
.map_err(|e| ManifestFreshError::MissingManifest {
|
||||
@@ -643,8 +656,13 @@ pub(crate) fn try_build_fresh_publication_point_with_timing(
|
||||
})?
|
||||
.ok_or_else(|| ManifestFreshError::MissingManifest {
|
||||
manifest_rsync_uri: manifest_rsync_uri.to_string(),
|
||||
})
|
||||
.inspect(|_| {
|
||||
timing.manifest_blob_load_ms =
|
||||
manifest_blob_load_started.elapsed().as_millis() as u64;
|
||||
})?
|
||||
} else {
|
||||
let manifest_blob_load_started = std::time::Instant::now();
|
||||
store
|
||||
.load_current_object_bytes_by_uri(manifest_rsync_uri)
|
||||
.map_err(|e| ManifestFreshError::MissingManifest {
|
||||
@@ -652,6 +670,10 @@ pub(crate) fn try_build_fresh_publication_point_with_timing(
|
||||
})?
|
||||
.ok_or_else(|| ManifestFreshError::MissingManifest {
|
||||
manifest_rsync_uri: manifest_rsync_uri.to_string(),
|
||||
})
|
||||
.inspect(|_| {
|
||||
timing.manifest_blob_load_ms =
|
||||
manifest_blob_load_started.elapsed().as_millis() as u64;
|
||||
})?
|
||||
};
|
||||
timing.manifest_load_ms = manifest_load_started.elapsed().as_millis() as u64;
|
||||
@@ -684,20 +706,21 @@ pub(crate) fn try_build_fresh_publication_point_with_timing(
|
||||
// - If manifestNumber is higher, require thisUpdate to be more recent than the previously
|
||||
// validated thisUpdate.
|
||||
let replay_guard_started = std::time::Instant::now();
|
||||
if let Some(old_vcir) = store.get_vcir(manifest_rsync_uri).ok().flatten() {
|
||||
if old_vcir.manifest_rsync_uri == manifest_rsync_uri {
|
||||
if let Some(old_meta) = store
|
||||
.get_manifest_replay_meta(manifest_rsync_uri)
|
||||
.ok()
|
||||
.flatten()
|
||||
{
|
||||
timing.replay_meta_hit = true;
|
||||
if old_meta.manifest_rsync_uri == manifest_rsync_uri {
|
||||
let new_num = manifest.manifest.manifest_number.bytes_be.as_slice();
|
||||
let old_num = old_vcir
|
||||
.validated_manifest_meta
|
||||
.validated_manifest_number
|
||||
.as_slice();
|
||||
let old_num = old_meta.manifest_number_be.as_slice();
|
||||
match cmp_minimal_be_unsigned(new_num, old_num) {
|
||||
Ordering::Greater => {
|
||||
let old_this_update = old_vcir
|
||||
.validated_manifest_meta
|
||||
.validated_manifest_this_update
|
||||
let old_this_update = old_meta
|
||||
.manifest_this_update
|
||||
.parse()
|
||||
.expect("vcir internal validation ensures thisUpdate parses");
|
||||
.expect("manifest replay meta validation ensures thisUpdate parses");
|
||||
if this_update <= old_this_update {
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
return Err(ManifestFreshError::ThisUpdateNotIncreasing {
|
||||
@@ -712,14 +735,8 @@ pub(crate) fn try_build_fresh_publication_point_with_timing(
|
||||
}
|
||||
}
|
||||
Ordering::Equal => {
|
||||
let old_manifest_hash =
|
||||
old_vcir.related_artifacts.iter().find_map(|artifact| {
|
||||
(artifact.artifact_role == VcirArtifactRole::Manifest
|
||||
&& artifact.uri.as_deref() == Some(manifest_rsync_uri))
|
||||
.then_some(artifact.sha256.as_str())
|
||||
});
|
||||
let new_manifest_hash = hex::encode(sha2::Sha256::digest(&manifest_bytes));
|
||||
if old_manifest_hash != Some(new_manifest_hash.as_str()) {
|
||||
let new_manifest_hash = sha2::Sha256::digest(&manifest_bytes);
|
||||
if old_meta.manifest_sha256.as_slice() != new_manifest_hash.as_slice() {
|
||||
return Err(ManifestFreshError::ManifestNumberNotIncreasing {
|
||||
old_hex: hex::encode_upper(old_num),
|
||||
new_hex: hex::encode_upper(new_num),
|
||||
@@ -734,6 +751,8 @@ pub(crate) fn try_build_fresh_publication_point_with_timing(
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
timing.replay_meta_miss = true;
|
||||
}
|
||||
timing.replay_guard_ms = replay_guard_started.elapsed().as_millis() as u64;
|
||||
|
||||
@@ -743,6 +762,7 @@ pub(crate) fn try_build_fresh_publication_point_with_timing(
|
||||
.parse_files()
|
||||
.map_err(ManifestDecodeError::Validate)?;
|
||||
timing.manifest_entries_ms = manifest_entries_started.elapsed().as_millis() as u64;
|
||||
timing.manifest_file_count = entries.len();
|
||||
let mut files = Vec::with_capacity(manifest.manifest.file_count());
|
||||
let pack_files_started = std::time::Instant::now();
|
||||
let external_raw_store = store
|
||||
@@ -753,22 +773,27 @@ pub(crate) fn try_build_fresh_publication_point_with_timing(
|
||||
.external_repo_bytes_ref()
|
||||
.cloned()
|
||||
.map(std::sync::Arc::new);
|
||||
let mut pack_files_index_lookup_duration = std::time::Duration::ZERO;
|
||||
let mut pack_files_blob_load_duration = std::time::Duration::ZERO;
|
||||
for entry in &entries {
|
||||
let rsync_uri =
|
||||
join_rsync_dir_and_file(publication_point_rsync_uri, entry.file_name.as_str());
|
||||
let current_object = if let Some(index) = current_index_guard.as_ref() {
|
||||
let index_lookup_started = std::time::Instant::now();
|
||||
let current =
|
||||
index
|
||||
.get_by_uri(&rsync_uri)
|
||||
.ok_or_else(|| ManifestFreshError::MissingFile {
|
||||
rsync_uri: rsync_uri.clone(),
|
||||
})?;
|
||||
pack_files_index_lookup_duration += index_lookup_started.elapsed();
|
||||
crate::storage::CurrentObjectWithHash {
|
||||
current_hash_hex: current.current_hash_hex.clone(),
|
||||
current_hash: current.current_hash,
|
||||
bytes: Vec::new(),
|
||||
}
|
||||
} else {
|
||||
let blob_load_started = std::time::Instant::now();
|
||||
store
|
||||
.load_current_object_with_hash_by_uri(&rsync_uri)
|
||||
.map_err(|_e| ManifestFreshError::MissingFile {
|
||||
@@ -776,6 +801,9 @@ pub(crate) fn try_build_fresh_publication_point_with_timing(
|
||||
})?
|
||||
.ok_or_else(|| ManifestFreshError::MissingFile {
|
||||
rsync_uri: rsync_uri.clone(),
|
||||
})
|
||||
.inspect(|_| {
|
||||
pack_files_blob_load_duration += blob_load_started.elapsed();
|
||||
})?
|
||||
};
|
||||
|
||||
@@ -803,6 +831,7 @@ pub(crate) fn try_build_fresh_publication_point_with_timing(
|
||||
));
|
||||
} else {
|
||||
let bytes = if current_object.bytes.is_empty() {
|
||||
let blob_load_started = std::time::Instant::now();
|
||||
store
|
||||
.get_blob_bytes(¤t_object.current_hash_hex)
|
||||
.map_err(|_e| ManifestFreshError::MissingFile {
|
||||
@@ -810,6 +839,9 @@ pub(crate) fn try_build_fresh_publication_point_with_timing(
|
||||
})?
|
||||
.ok_or_else(|| ManifestFreshError::MissingFile {
|
||||
rsync_uri: rsync_uri.clone(),
|
||||
})
|
||||
.inspect(|_| {
|
||||
pack_files_blob_load_duration += blob_load_started.elapsed();
|
||||
})?
|
||||
} else {
|
||||
current_object.bytes
|
||||
@@ -821,6 +853,8 @@ pub(crate) fn try_build_fresh_publication_point_with_timing(
|
||||
));
|
||||
}
|
||||
}
|
||||
timing.pack_files_index_lookup_ms = pack_files_index_lookup_duration.as_millis() as u64;
|
||||
timing.pack_files_blob_load_ms = pack_files_blob_load_duration.as_millis() as u64;
|
||||
timing.pack_files_ms = pack_files_started.elapsed().as_millis() as u64;
|
||||
|
||||
// RFC 6488 §3: manifest (signed object) validity includes a valid EE cert path.
|
||||
@@ -1044,12 +1078,67 @@ mod tests {
|
||||
.expect("put repository view entry");
|
||||
}
|
||||
|
||||
fn put_complete_publication_point_current_objects(
|
||||
store: &RocksStore,
|
||||
manifest: &ManifestObject,
|
||||
manifest_rsync_uri: &str,
|
||||
manifest_bytes: Vec<u8>,
|
||||
publication_point_rsync_uri: &str,
|
||||
) {
|
||||
put_current_object(store, manifest_rsync_uri, manifest_bytes, "mft");
|
||||
for entry in manifest.manifest.parse_files().expect("parse files") {
|
||||
let file_path = manifest_fixture_path()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(entry.file_name.as_str());
|
||||
let bytes = std::fs::read(&file_path).expect("read fixture file");
|
||||
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
|
||||
let object_type = rsync_uri.rsplit('.').next().unwrap_or("bin");
|
||||
put_current_object(store, &rsync_uri, bytes, object_type);
|
||||
}
|
||||
}
|
||||
|
||||
fn put_raw_only(store: &RocksStore, rsync_uri: &str, bytes: Vec<u8>, object_type: &str) {
|
||||
store
|
||||
.put_raw_by_hash_entry(&raw_by_hash_entry(rsync_uri, bytes, object_type))
|
||||
.expect("put raw_by_hash entry");
|
||||
}
|
||||
|
||||
fn sample_vcir_for_manifest_replay_meta(
|
||||
manifest: &ManifestObject,
|
||||
manifest_rsync_uri: &str,
|
||||
publication_point_rsync_uri: &str,
|
||||
manifest_bytes: &[u8],
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> ValidatedCaInstanceResult {
|
||||
let manifest_hash = hex::encode(sha2::Sha256::digest(manifest_bytes));
|
||||
let this_update = manifest
|
||||
.manifest
|
||||
.this_update
|
||||
.to_offset(time::UtcOffset::UTC);
|
||||
let mut vcir = sample_current_instance_vcir(
|
||||
manifest_rsync_uri,
|
||||
publication_point_rsync_uri,
|
||||
&manifest_hash,
|
||||
"rsync://example.test/repo/object.roa",
|
||||
&hex::encode(sha2::Sha256::digest(b"object")),
|
||||
validation_time,
|
||||
true,
|
||||
);
|
||||
vcir.validated_manifest_meta.validated_manifest_number =
|
||||
manifest.manifest.manifest_number.bytes_be.clone();
|
||||
vcir.validated_manifest_meta.validated_manifest_this_update =
|
||||
PackTime::from_utc_offset_datetime(this_update);
|
||||
vcir.ccr_manifest_projection.manifest_number_be =
|
||||
manifest.manifest.manifest_number.bytes_be.clone();
|
||||
vcir.ccr_manifest_projection.manifest_this_update =
|
||||
PackTime::from_utc_offset_datetime(this_update);
|
||||
vcir.ccr_manifest_projection.manifest_sha256 =
|
||||
hex::decode(manifest_hash).expect("decode manifest hash");
|
||||
vcir.ccr_manifest_projection.manifest_size = manifest_bytes.len() as u64;
|
||||
vcir
|
||||
}
|
||||
|
||||
fn sample_current_instance_vcir(
|
||||
manifest_rsync_uri: &str,
|
||||
publication_point_rsync_uri: &str,
|
||||
@@ -1371,6 +1460,82 @@ mod tests {
|
||||
assert_eq!(fresh.files.len(), manifest.manifest.file_count());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_build_fresh_publication_point_records_replay_meta_miss() {
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
let store = RocksStore::open(temp.path()).expect("open rocksdb");
|
||||
let (
|
||||
manifest,
|
||||
manifest_bytes,
|
||||
manifest_rsync_uri,
|
||||
publication_point_rsync_uri,
|
||||
validation_time,
|
||||
) = load_manifest_fixture();
|
||||
put_complete_publication_point_current_objects(
|
||||
&store,
|
||||
&manifest,
|
||||
&manifest_rsync_uri,
|
||||
manifest_bytes,
|
||||
&publication_point_rsync_uri,
|
||||
);
|
||||
|
||||
let (_fresh, timing) = try_build_fresh_publication_point_with_timing(
|
||||
&store,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
None,
|
||||
&issuer_ca_fixture_der(),
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.expect("fresh publication point without replay meta");
|
||||
|
||||
assert!(timing.replay_meta_miss);
|
||||
assert!(!timing.replay_meta_hit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_build_fresh_publication_point_uses_replay_meta_hit_for_same_manifest() {
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
let store = RocksStore::open(temp.path()).expect("open rocksdb");
|
||||
let (
|
||||
manifest,
|
||||
manifest_bytes,
|
||||
manifest_rsync_uri,
|
||||
publication_point_rsync_uri,
|
||||
validation_time,
|
||||
) = load_manifest_fixture();
|
||||
let previous_vcir = sample_vcir_for_manifest_replay_meta(
|
||||
&manifest,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
&manifest_bytes,
|
||||
validation_time,
|
||||
);
|
||||
store.put_vcir(&previous_vcir).expect("put previous vcir");
|
||||
put_complete_publication_point_current_objects(
|
||||
&store,
|
||||
&manifest,
|
||||
&manifest_rsync_uri,
|
||||
manifest_bytes,
|
||||
&publication_point_rsync_uri,
|
||||
);
|
||||
|
||||
let (_fresh, timing) = try_build_fresh_publication_point_with_timing(
|
||||
&store,
|
||||
&manifest_rsync_uri,
|
||||
&publication_point_rsync_uri,
|
||||
None,
|
||||
&issuer_ca_fixture_der(),
|
||||
Some(issuer_ca_rsync_uri()),
|
||||
validation_time,
|
||||
)
|
||||
.expect("fresh publication point with matching replay meta");
|
||||
|
||||
assert!(timing.replay_meta_hit);
|
||||
assert!(!timing.replay_meta_miss);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_manifest_embedded_ee_cert_path_rejects_missing_crl_files() {
|
||||
let (manifest, _, _, publication_point_rsync_uri, validation_time) =
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::validation::cert_path::{CertPathError, validate_signed_object_ee_cert
|
||||
use crate::validation::manifest::PublicationPointData;
|
||||
use crate::validation::publication_point::PublicationPointSnapshot;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
use x509_parser::prelude::FromDer;
|
||||
use x509_parser::x509::SubjectPublicKeyInfo;
|
||||
|
||||
@@ -118,6 +118,9 @@ pub(crate) struct RoaTaskOk {
|
||||
pub(crate) struct RoaTaskResult {
|
||||
pub(crate) publication_point_id: u64,
|
||||
pub(crate) index: usize,
|
||||
pub(crate) worker_index: usize,
|
||||
pub(crate) queue_wait_ms: u64,
|
||||
pub(crate) worker_ms: u64,
|
||||
pub(crate) rsync_uri: String,
|
||||
pub(crate) sha256_hex: String,
|
||||
pub(crate) outcome: Result<RoaTaskOk, ObjectValidateError>,
|
||||
@@ -736,14 +739,15 @@ pub(crate) struct OwnedRoaTask {
|
||||
issuer_effective_as: Option<crate::data_model::rc::AsResourceSet>,
|
||||
validation_time: time::OffsetDateTime,
|
||||
collect_vcir_local_outputs: bool,
|
||||
pub(crate) submitted_at: Option<Instant>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RoaTaskExecutor;
|
||||
|
||||
impl ObjectTaskExecutor<OwnedRoaTask, RoaTaskResult> for RoaTaskExecutor {
|
||||
fn execute(&self, _worker_index: usize, task: OwnedRoaTask) -> RoaTaskResult {
|
||||
validate_owned_roa_task(task)
|
||||
fn execute(&self, worker_index: usize, task: OwnedRoaTask) -> RoaTaskResult {
|
||||
validate_owned_roa_task(worker_index, task)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -787,7 +791,13 @@ impl ParallelRoaWorkerPool {
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_owned_roa_task(task: OwnedRoaTask) -> RoaTaskResult {
|
||||
fn validate_owned_roa_task(worker_index: usize, task: OwnedRoaTask) -> RoaTaskResult {
|
||||
let worker_started = Instant::now();
|
||||
let queue_wait_ms = task
|
||||
.submitted_at
|
||||
.map(|submitted_at| worker_started.saturating_duration_since(submitted_at))
|
||||
.map(|duration| duration.as_millis() as u64)
|
||||
.unwrap_or(0);
|
||||
let sha256_hex = sha256_hex_from_32(&task.file.sha256);
|
||||
let issuer_spki = match SubjectPublicKeyInfo::from_der(task.issuer_spki_der.as_ref()) {
|
||||
Ok((rem, spki)) if rem.is_empty() => spki,
|
||||
@@ -795,6 +805,9 @@ fn validate_owned_roa_task(task: OwnedRoaTask) -> RoaTaskResult {
|
||||
return RoaTaskResult {
|
||||
publication_point_id: task.publication_point_id,
|
||||
index: task.index,
|
||||
worker_index,
|
||||
queue_wait_ms,
|
||||
worker_ms: worker_started.elapsed().as_millis() as u64,
|
||||
rsync_uri: task.file.rsync_uri,
|
||||
sha256_hex,
|
||||
outcome: Err(ObjectValidateError::CertPath(
|
||||
@@ -806,6 +819,9 @@ fn validate_owned_roa_task(task: OwnedRoaTask) -> RoaTaskResult {
|
||||
return RoaTaskResult {
|
||||
publication_point_id: task.publication_point_id,
|
||||
index: task.index,
|
||||
worker_index,
|
||||
queue_wait_ms,
|
||||
worker_ms: worker_started.elapsed().as_millis() as u64,
|
||||
rsync_uri: task.file.rsync_uri,
|
||||
sha256_hex,
|
||||
outcome: Err(ObjectValidateError::CertPath(
|
||||
@@ -837,6 +853,9 @@ fn validate_owned_roa_task(task: OwnedRoaTask) -> RoaTaskResult {
|
||||
RoaTaskResult {
|
||||
publication_point_id: task.publication_point_id,
|
||||
index: task.index,
|
||||
worker_index,
|
||||
queue_wait_ms,
|
||||
worker_ms: worker_started.elapsed().as_millis() as u64,
|
||||
rsync_uri: task.file.rsync_uri,
|
||||
sha256_hex,
|
||||
outcome,
|
||||
@@ -888,6 +907,7 @@ impl ParallelObjectsStage {
|
||||
issuer_effective_as: self.issuer_effective_as.clone(),
|
||||
validation_time: self.validation_time,
|
||||
collect_vcir_local_outputs: self.collect_vcir_local_outputs,
|
||||
submitted_at: None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -895,6 +915,14 @@ impl ParallelObjectsStage {
|
||||
pub(crate) fn roa_task_count(&self) -> usize {
|
||||
self.stats.roa_total
|
||||
}
|
||||
|
||||
pub(crate) fn aspa_task_count(&self) -> usize {
|
||||
self.stats.aspa_total
|
||||
}
|
||||
|
||||
pub(crate) fn locked_file_count(&self) -> usize {
|
||||
self.locked_files.len()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn prepare_publication_point_for_parallel_roa<P: PublicationPointData>(
|
||||
@@ -1408,6 +1436,9 @@ pub(crate) fn validate_roa_task_serial(
|
||||
RoaTaskResult {
|
||||
publication_point_id: 0,
|
||||
index: task.index,
|
||||
worker_index: 0,
|
||||
queue_wait_ms: 0,
|
||||
worker_ms: 0,
|
||||
rsync_uri: task.file.rsync_uri.clone(),
|
||||
sha256_hex,
|
||||
outcome,
|
||||
|
||||
@@ -41,6 +41,15 @@ struct InflightPublicationPoint {
|
||||
started_at: Instant,
|
||||
objects_started_at: Instant,
|
||||
task_count: usize,
|
||||
tasks_submitted: usize,
|
||||
first_task_submitted_at: Option<Instant>,
|
||||
last_task_submitted_at: Option<Instant>,
|
||||
first_result_at: Option<Instant>,
|
||||
last_result_at: Option<Instant>,
|
||||
worker_ms_total: u64,
|
||||
worker_ms_max: u64,
|
||||
queue_wait_ms_total: u64,
|
||||
queue_wait_ms_max: u64,
|
||||
results: Vec<crate::validation::objects::RoaTaskResult>,
|
||||
}
|
||||
|
||||
@@ -49,6 +58,220 @@ struct FinishedPublicationPoint {
|
||||
result: Result<PublicationPointRunResult, String>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ReadyStageMetrics {
|
||||
manifest_rsync_uri: Option<String>,
|
||||
publication_point_rsync_uri: Option<String>,
|
||||
ready_count: usize,
|
||||
fallback_count: usize,
|
||||
complete_count: usize,
|
||||
staged_count: usize,
|
||||
zero_task_count: usize,
|
||||
error_count: usize,
|
||||
discovered_children: usize,
|
||||
locked_files: usize,
|
||||
roa_tasks: usize,
|
||||
aspa_objects: usize,
|
||||
stage_fresh_ms: u64,
|
||||
snapshot_prepare_ms: u64,
|
||||
snapshot_current_index_lock_ms: u64,
|
||||
snapshot_manifest_load_ms: u64,
|
||||
snapshot_manifest_index_lookup_ms: u64,
|
||||
snapshot_manifest_blob_load_ms: u64,
|
||||
snapshot_manifest_decode_ms: u64,
|
||||
snapshot_replay_guard_ms: u64,
|
||||
replay_meta_hit_count: usize,
|
||||
replay_meta_miss_count: usize,
|
||||
snapshot_manifest_entries_ms: u64,
|
||||
snapshot_pack_files_ms: u64,
|
||||
snapshot_pack_files_index_lookup_ms: u64,
|
||||
snapshot_pack_files_blob_load_ms: u64,
|
||||
snapshot_ee_path_validate_ms: u64,
|
||||
snapshot_manifest_file_count: usize,
|
||||
child_discovery_ms: u64,
|
||||
child_enqueue_ms: u64,
|
||||
prepare_ms: u64,
|
||||
build_roa_tasks_ms: u64,
|
||||
total_ms: u64,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ReadyStageBatchMetrics {
|
||||
ready_count: usize,
|
||||
fallback_count: usize,
|
||||
complete_count: usize,
|
||||
staged_count: usize,
|
||||
zero_task_count: usize,
|
||||
error_count: usize,
|
||||
discovered_children: usize,
|
||||
locked_files: usize,
|
||||
roa_tasks: usize,
|
||||
aspa_objects: usize,
|
||||
stage_fresh_ms_total: u64,
|
||||
stage_fresh_ms_max: u64,
|
||||
stage_fresh_ms_max_manifest_rsync_uri: Option<String>,
|
||||
stage_fresh_ms_max_publication_point_rsync_uri: Option<String>,
|
||||
snapshot_prepare_ms_total: u64,
|
||||
snapshot_prepare_ms_max: u64,
|
||||
snapshot_current_index_lock_ms_total: u64,
|
||||
snapshot_current_index_lock_ms_max: u64,
|
||||
snapshot_manifest_load_ms_total: u64,
|
||||
snapshot_manifest_load_ms_max: u64,
|
||||
snapshot_manifest_index_lookup_ms_total: u64,
|
||||
snapshot_manifest_index_lookup_ms_max: u64,
|
||||
snapshot_manifest_blob_load_ms_total: u64,
|
||||
snapshot_manifest_blob_load_ms_max: u64,
|
||||
snapshot_manifest_decode_ms_total: u64,
|
||||
snapshot_manifest_decode_ms_max: u64,
|
||||
snapshot_replay_guard_ms_total: u64,
|
||||
snapshot_replay_guard_ms_max: u64,
|
||||
replay_meta_hit_count: usize,
|
||||
replay_meta_miss_count: usize,
|
||||
snapshot_manifest_entries_ms_total: u64,
|
||||
snapshot_manifest_entries_ms_max: u64,
|
||||
snapshot_pack_files_ms_total: u64,
|
||||
snapshot_pack_files_ms_max: u64,
|
||||
snapshot_pack_files_index_lookup_ms_total: u64,
|
||||
snapshot_pack_files_index_lookup_ms_max: u64,
|
||||
snapshot_pack_files_blob_load_ms_total: u64,
|
||||
snapshot_pack_files_blob_load_ms_max: u64,
|
||||
snapshot_ee_path_validate_ms_total: u64,
|
||||
snapshot_ee_path_validate_ms_max: u64,
|
||||
snapshot_manifest_file_count_total: usize,
|
||||
snapshot_manifest_file_count_max: usize,
|
||||
child_discovery_ms_total: u64,
|
||||
child_discovery_ms_max: u64,
|
||||
child_enqueue_ms_total: u64,
|
||||
child_enqueue_ms_max: u64,
|
||||
prepare_ms_total: u64,
|
||||
prepare_ms_max: u64,
|
||||
build_roa_tasks_ms_total: u64,
|
||||
build_roa_tasks_ms_max: u64,
|
||||
total_ms: u64,
|
||||
}
|
||||
|
||||
impl ReadyStageBatchMetrics {
|
||||
fn record(&mut self, metrics: ReadyStageMetrics) {
|
||||
self.ready_count += metrics.ready_count;
|
||||
self.fallback_count += metrics.fallback_count;
|
||||
self.complete_count += metrics.complete_count;
|
||||
self.staged_count += metrics.staged_count;
|
||||
self.zero_task_count += metrics.zero_task_count;
|
||||
self.error_count += metrics.error_count;
|
||||
self.discovered_children += metrics.discovered_children;
|
||||
self.locked_files += metrics.locked_files;
|
||||
self.roa_tasks += metrics.roa_tasks;
|
||||
self.aspa_objects += metrics.aspa_objects;
|
||||
if metrics.stage_fresh_ms >= self.stage_fresh_ms_max {
|
||||
self.stage_fresh_ms_max_manifest_rsync_uri = metrics.manifest_rsync_uri.clone();
|
||||
self.stage_fresh_ms_max_publication_point_rsync_uri =
|
||||
metrics.publication_point_rsync_uri.clone();
|
||||
}
|
||||
self.stage_fresh_ms_total += metrics.stage_fresh_ms;
|
||||
self.stage_fresh_ms_max = self.stage_fresh_ms_max.max(metrics.stage_fresh_ms);
|
||||
self.snapshot_prepare_ms_total += metrics.snapshot_prepare_ms;
|
||||
self.snapshot_prepare_ms_max = self
|
||||
.snapshot_prepare_ms_max
|
||||
.max(metrics.snapshot_prepare_ms);
|
||||
self.snapshot_current_index_lock_ms_total += metrics.snapshot_current_index_lock_ms;
|
||||
self.snapshot_current_index_lock_ms_max = self
|
||||
.snapshot_current_index_lock_ms_max
|
||||
.max(metrics.snapshot_current_index_lock_ms);
|
||||
self.snapshot_manifest_load_ms_total += metrics.snapshot_manifest_load_ms;
|
||||
self.snapshot_manifest_load_ms_max = self
|
||||
.snapshot_manifest_load_ms_max
|
||||
.max(metrics.snapshot_manifest_load_ms);
|
||||
self.snapshot_manifest_index_lookup_ms_total += metrics.snapshot_manifest_index_lookup_ms;
|
||||
self.snapshot_manifest_index_lookup_ms_max = self
|
||||
.snapshot_manifest_index_lookup_ms_max
|
||||
.max(metrics.snapshot_manifest_index_lookup_ms);
|
||||
self.snapshot_manifest_blob_load_ms_total += metrics.snapshot_manifest_blob_load_ms;
|
||||
self.snapshot_manifest_blob_load_ms_max = self
|
||||
.snapshot_manifest_blob_load_ms_max
|
||||
.max(metrics.snapshot_manifest_blob_load_ms);
|
||||
self.snapshot_manifest_decode_ms_total += metrics.snapshot_manifest_decode_ms;
|
||||
self.snapshot_manifest_decode_ms_max = self
|
||||
.snapshot_manifest_decode_ms_max
|
||||
.max(metrics.snapshot_manifest_decode_ms);
|
||||
self.snapshot_replay_guard_ms_total += metrics.snapshot_replay_guard_ms;
|
||||
self.snapshot_replay_guard_ms_max = self
|
||||
.snapshot_replay_guard_ms_max
|
||||
.max(metrics.snapshot_replay_guard_ms);
|
||||
self.replay_meta_hit_count += metrics.replay_meta_hit_count;
|
||||
self.replay_meta_miss_count += metrics.replay_meta_miss_count;
|
||||
self.snapshot_manifest_entries_ms_total += metrics.snapshot_manifest_entries_ms;
|
||||
self.snapshot_manifest_entries_ms_max = self
|
||||
.snapshot_manifest_entries_ms_max
|
||||
.max(metrics.snapshot_manifest_entries_ms);
|
||||
self.snapshot_pack_files_ms_total += metrics.snapshot_pack_files_ms;
|
||||
self.snapshot_pack_files_ms_max = self
|
||||
.snapshot_pack_files_ms_max
|
||||
.max(metrics.snapshot_pack_files_ms);
|
||||
self.snapshot_pack_files_index_lookup_ms_total +=
|
||||
metrics.snapshot_pack_files_index_lookup_ms;
|
||||
self.snapshot_pack_files_index_lookup_ms_max = self
|
||||
.snapshot_pack_files_index_lookup_ms_max
|
||||
.max(metrics.snapshot_pack_files_index_lookup_ms);
|
||||
self.snapshot_pack_files_blob_load_ms_total += metrics.snapshot_pack_files_blob_load_ms;
|
||||
self.snapshot_pack_files_blob_load_ms_max = self
|
||||
.snapshot_pack_files_blob_load_ms_max
|
||||
.max(metrics.snapshot_pack_files_blob_load_ms);
|
||||
self.snapshot_ee_path_validate_ms_total += metrics.snapshot_ee_path_validate_ms;
|
||||
self.snapshot_ee_path_validate_ms_max = self
|
||||
.snapshot_ee_path_validate_ms_max
|
||||
.max(metrics.snapshot_ee_path_validate_ms);
|
||||
self.snapshot_manifest_file_count_total += metrics.snapshot_manifest_file_count;
|
||||
self.snapshot_manifest_file_count_max = self
|
||||
.snapshot_manifest_file_count_max
|
||||
.max(metrics.snapshot_manifest_file_count);
|
||||
self.child_discovery_ms_total += metrics.child_discovery_ms;
|
||||
self.child_discovery_ms_max = self.child_discovery_ms_max.max(metrics.child_discovery_ms);
|
||||
self.child_enqueue_ms_total += metrics.child_enqueue_ms;
|
||||
self.child_enqueue_ms_max = self.child_enqueue_ms_max.max(metrics.child_enqueue_ms);
|
||||
self.prepare_ms_total += metrics.prepare_ms;
|
||||
self.prepare_ms_max = self.prepare_ms_max.max(metrics.prepare_ms);
|
||||
self.build_roa_tasks_ms_total += metrics.build_roa_tasks_ms;
|
||||
self.build_roa_tasks_ms_max = self.build_roa_tasks_ms_max.max(metrics.build_roa_tasks_ms);
|
||||
self.total_ms += metrics.total_ms;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct RoaDispatchMetrics {
|
||||
attempted: usize,
|
||||
submitted: usize,
|
||||
queue_full: bool,
|
||||
pending_remaining: usize,
|
||||
duration_ms: u64,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ObjectDrainMetrics {
|
||||
results_drained: usize,
|
||||
publication_points_finalized: usize,
|
||||
reduce_ms_total: u64,
|
||||
reduce_ms_max: u64,
|
||||
finalize_ms_total: u64,
|
||||
finalize_ms_max: u64,
|
||||
worker_ms_total: u64,
|
||||
worker_ms_max: u64,
|
||||
queue_wait_ms_total: u64,
|
||||
queue_wait_ms_max: u64,
|
||||
duration_ms: u64,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct RepoDrainMetrics {
|
||||
event_count: usize,
|
||||
completions: usize,
|
||||
ready_enqueued: usize,
|
||||
duration_ms: u64,
|
||||
}
|
||||
|
||||
fn elapsed_ms(started: Instant) -> u64 {
|
||||
started.elapsed().as_millis() as u64
|
||||
}
|
||||
|
||||
fn compact_phase2_finished_result(
|
||||
mut result: PublicationPointRunResult,
|
||||
compact_audit: bool,
|
||||
@@ -109,6 +332,12 @@ pub fn run_tree_parallel_phase2_audit_multi_root(
|
||||
let mut pending_roa_dispatch: VecDeque<OwnedRoaTask> = VecDeque::new();
|
||||
let mut finished: Vec<FinishedPublicationPoint> = Vec::new();
|
||||
let mut instances_started = 0usize;
|
||||
let ready_batch_size = runner
|
||||
.parallel_phase2_config
|
||||
.as_ref()
|
||||
.map(|cfg| cfg.ready_batch_size)
|
||||
.unwrap_or(256)
|
||||
.max(1);
|
||||
|
||||
loop {
|
||||
while can_start_more(instances_started, config) {
|
||||
@@ -149,8 +378,13 @@ pub fn run_tree_parallel_phase2_audit_multi_root(
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(ready) = ready_queue.pop_front() {
|
||||
stage_ready_publication_point(
|
||||
let ready_batch_started = Instant::now();
|
||||
let mut ready_batch_metrics = ReadyStageBatchMetrics::default();
|
||||
while ready_batch_metrics.ready_count < ready_batch_size {
|
||||
let Some(ready) = ready_queue.pop_front() else {
|
||||
break;
|
||||
};
|
||||
let metrics = stage_ready_publication_point(
|
||||
runner,
|
||||
&mut next_id,
|
||||
&mut ca_queue,
|
||||
@@ -160,15 +394,129 @@ pub fn run_tree_parallel_phase2_audit_multi_root(
|
||||
ready,
|
||||
config.compact_audit,
|
||||
);
|
||||
ready_batch_metrics.record(metrics);
|
||||
}
|
||||
if ready_batch_metrics.ready_count > 0 {
|
||||
ready_batch_metrics.total_ms = elapsed_ms(ready_batch_started);
|
||||
crate::progress_log::emit(
|
||||
"phase2_ready_queue_batch",
|
||||
serde_json::json!({
|
||||
"ready_count": ready_batch_metrics.ready_count,
|
||||
"fallback_count": ready_batch_metrics.fallback_count,
|
||||
"complete_count": ready_batch_metrics.complete_count,
|
||||
"staged_count": ready_batch_metrics.staged_count,
|
||||
"zero_task_count": ready_batch_metrics.zero_task_count,
|
||||
"error_count": ready_batch_metrics.error_count,
|
||||
"discovered_children": ready_batch_metrics.discovered_children,
|
||||
"locked_files": ready_batch_metrics.locked_files,
|
||||
"roa_tasks": ready_batch_metrics.roa_tasks,
|
||||
"aspa_objects": ready_batch_metrics.aspa_objects,
|
||||
"stage_fresh_ms_total": ready_batch_metrics.stage_fresh_ms_total,
|
||||
"stage_fresh_ms_max": ready_batch_metrics.stage_fresh_ms_max,
|
||||
"stage_fresh_ms_max_manifest_rsync_uri": ready_batch_metrics.stage_fresh_ms_max_manifest_rsync_uri,
|
||||
"stage_fresh_ms_max_publication_point_rsync_uri": ready_batch_metrics.stage_fresh_ms_max_publication_point_rsync_uri,
|
||||
"child_enqueue_ms_total": ready_batch_metrics.child_enqueue_ms_total,
|
||||
"child_enqueue_ms_max": ready_batch_metrics.child_enqueue_ms_max,
|
||||
"prepare_ms_total": ready_batch_metrics.prepare_ms_total,
|
||||
"prepare_ms_max": ready_batch_metrics.prepare_ms_max,
|
||||
"build_roa_tasks_ms_total": ready_batch_metrics.build_roa_tasks_ms_total,
|
||||
"build_roa_tasks_ms_max": ready_batch_metrics.build_roa_tasks_ms_max,
|
||||
"batch_duration_ms": ready_batch_metrics.total_ms,
|
||||
"ready_batch_size": ready_batch_size,
|
||||
"ready_queue_len_after_batch": ready_queue.len(),
|
||||
"ready_queue_budget_exhausted": !ready_queue.is_empty(),
|
||||
"ca_queue_len_after_batch": ca_queue.len(),
|
||||
"pending_roa_dispatch_len_after_batch": pending_roa_dispatch.len(),
|
||||
"inflight_publication_points_after_batch": inflight_publication_points.len(),
|
||||
}),
|
||||
);
|
||||
crate::progress_log::emit(
|
||||
"phase2_ready_queue_stage_fresh_breakdown",
|
||||
serde_json::json!({
|
||||
"ready_count": ready_batch_metrics.ready_count,
|
||||
"stage_fresh_ms_total": ready_batch_metrics.stage_fresh_ms_total,
|
||||
"stage_fresh_ms_max": ready_batch_metrics.stage_fresh_ms_max,
|
||||
"stage_fresh_ms_max_manifest_rsync_uri": ready_batch_metrics.stage_fresh_ms_max_manifest_rsync_uri,
|
||||
"stage_fresh_ms_max_publication_point_rsync_uri": ready_batch_metrics.stage_fresh_ms_max_publication_point_rsync_uri,
|
||||
"snapshot_prepare_ms_total": ready_batch_metrics.snapshot_prepare_ms_total,
|
||||
"snapshot_prepare_ms_max": ready_batch_metrics.snapshot_prepare_ms_max,
|
||||
"snapshot_current_index_lock_ms_total": ready_batch_metrics.snapshot_current_index_lock_ms_total,
|
||||
"snapshot_current_index_lock_ms_max": ready_batch_metrics.snapshot_current_index_lock_ms_max,
|
||||
"snapshot_manifest_load_ms_total": ready_batch_metrics.snapshot_manifest_load_ms_total,
|
||||
"snapshot_manifest_load_ms_max": ready_batch_metrics.snapshot_manifest_load_ms_max,
|
||||
"snapshot_manifest_index_lookup_ms_total": ready_batch_metrics.snapshot_manifest_index_lookup_ms_total,
|
||||
"snapshot_manifest_index_lookup_ms_max": ready_batch_metrics.snapshot_manifest_index_lookup_ms_max,
|
||||
"snapshot_manifest_blob_load_ms_total": ready_batch_metrics.snapshot_manifest_blob_load_ms_total,
|
||||
"snapshot_manifest_blob_load_ms_max": ready_batch_metrics.snapshot_manifest_blob_load_ms_max,
|
||||
"snapshot_manifest_decode_ms_total": ready_batch_metrics.snapshot_manifest_decode_ms_total,
|
||||
"snapshot_manifest_decode_ms_max": ready_batch_metrics.snapshot_manifest_decode_ms_max,
|
||||
"snapshot_replay_guard_ms_total": ready_batch_metrics.snapshot_replay_guard_ms_total,
|
||||
"snapshot_replay_guard_ms_max": ready_batch_metrics.snapshot_replay_guard_ms_max,
|
||||
"replay_meta_hit_count": ready_batch_metrics.replay_meta_hit_count,
|
||||
"replay_meta_miss_count": ready_batch_metrics.replay_meta_miss_count,
|
||||
"snapshot_manifest_entries_ms_total": ready_batch_metrics.snapshot_manifest_entries_ms_total,
|
||||
"snapshot_manifest_entries_ms_max": ready_batch_metrics.snapshot_manifest_entries_ms_max,
|
||||
"snapshot_pack_files_ms_total": ready_batch_metrics.snapshot_pack_files_ms_total,
|
||||
"snapshot_pack_files_ms_max": ready_batch_metrics.snapshot_pack_files_ms_max,
|
||||
"snapshot_pack_files_index_lookup_ms_total": ready_batch_metrics.snapshot_pack_files_index_lookup_ms_total,
|
||||
"snapshot_pack_files_index_lookup_ms_max": ready_batch_metrics.snapshot_pack_files_index_lookup_ms_max,
|
||||
"snapshot_pack_files_blob_load_ms_total": ready_batch_metrics.snapshot_pack_files_blob_load_ms_total,
|
||||
"snapshot_pack_files_blob_load_ms_max": ready_batch_metrics.snapshot_pack_files_blob_load_ms_max,
|
||||
"snapshot_ee_path_validate_ms_total": ready_batch_metrics.snapshot_ee_path_validate_ms_total,
|
||||
"snapshot_ee_path_validate_ms_max": ready_batch_metrics.snapshot_ee_path_validate_ms_max,
|
||||
"snapshot_manifest_file_count_total": ready_batch_metrics.snapshot_manifest_file_count_total,
|
||||
"snapshot_manifest_file_count_max": ready_batch_metrics.snapshot_manifest_file_count_max,
|
||||
"child_discovery_ms_total": ready_batch_metrics.child_discovery_ms_total,
|
||||
"child_discovery_ms_max": ready_batch_metrics.child_discovery_ms_max,
|
||||
"batch_duration_ms": ready_batch_metrics.total_ms,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
flush_pending_roa_dispatch(runner, &mut pending_roa_dispatch)?;
|
||||
drain_object_results(
|
||||
let dispatch_metrics = flush_pending_roa_dispatch(
|
||||
runner,
|
||||
&mut pending_roa_dispatch,
|
||||
&mut inflight_publication_points,
|
||||
)?;
|
||||
if dispatch_metrics.attempted > 0 || dispatch_metrics.queue_full {
|
||||
crate::progress_log::emit(
|
||||
"phase2_roa_dispatch_batch",
|
||||
serde_json::json!({
|
||||
"attempted": dispatch_metrics.attempted,
|
||||
"submitted": dispatch_metrics.submitted,
|
||||
"queue_full": dispatch_metrics.queue_full,
|
||||
"pending_remaining": dispatch_metrics.pending_remaining,
|
||||
"duration_ms": dispatch_metrics.duration_ms,
|
||||
"inflight_publication_points": inflight_publication_points.len(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
let drain_metrics = drain_object_results(
|
||||
runner,
|
||||
&mut inflight_publication_points,
|
||||
&mut finished,
|
||||
config.compact_audit,
|
||||
)?;
|
||||
if drain_metrics.results_drained > 0 {
|
||||
crate::progress_log::emit(
|
||||
"phase2_object_results_drain",
|
||||
serde_json::json!({
|
||||
"results_drained": drain_metrics.results_drained,
|
||||
"publication_points_finalized": drain_metrics.publication_points_finalized,
|
||||
"reduce_ms_total": drain_metrics.reduce_ms_total,
|
||||
"reduce_ms_max": drain_metrics.reduce_ms_max,
|
||||
"finalize_ms_total": drain_metrics.finalize_ms_total,
|
||||
"finalize_ms_max": drain_metrics.finalize_ms_max,
|
||||
"worker_ms_total": drain_metrics.worker_ms_total,
|
||||
"worker_ms_max": drain_metrics.worker_ms_max,
|
||||
"queue_wait_ms_total": drain_metrics.queue_wait_ms_total,
|
||||
"queue_wait_ms_max": drain_metrics.queue_wait_ms_max,
|
||||
"duration_ms": drain_metrics.duration_ms,
|
||||
"pending_roa_dispatch_len": pending_roa_dispatch.len(),
|
||||
"inflight_publication_points": inflight_publication_points.len(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
let repo_poll_timeout = event_poll_timeout(
|
||||
&ca_queue,
|
||||
&ready_queue,
|
||||
@@ -177,12 +525,25 @@ pub fn run_tree_parallel_phase2_audit_multi_root(
|
||||
instances_started,
|
||||
config,
|
||||
);
|
||||
drain_repo_events(
|
||||
let repo_metrics = drain_repo_events(
|
||||
repo_runtime.as_ref(),
|
||||
&mut ca_waiting_repo_by_identity,
|
||||
&mut ready_queue,
|
||||
repo_poll_timeout,
|
||||
)?;
|
||||
if repo_metrics.event_count > 0 {
|
||||
crate::progress_log::emit(
|
||||
"phase2_repo_events_drain",
|
||||
serde_json::json!({
|
||||
"event_count": repo_metrics.event_count,
|
||||
"completions": repo_metrics.completions,
|
||||
"ready_enqueued": repo_metrics.ready_enqueued,
|
||||
"duration_ms": repo_metrics.duration_ms,
|
||||
"ready_queue_len": ready_queue.len(),
|
||||
"ca_waiting_repo_identities": ca_waiting_repo_by_identity.len(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if is_complete(
|
||||
&ca_queue,
|
||||
@@ -219,21 +580,48 @@ fn stage_ready_publication_point(
|
||||
finished: &mut Vec<FinishedPublicationPoint>,
|
||||
ready: ReadyCaInstance,
|
||||
compact_audit: bool,
|
||||
) {
|
||||
) -> ReadyStageMetrics {
|
||||
let publication_point_started = Instant::now();
|
||||
let mut metrics = ReadyStageMetrics {
|
||||
ready_count: 1,
|
||||
manifest_rsync_uri: Some(ready.node.handle.manifest_rsync_uri.clone()),
|
||||
publication_point_rsync_uri: Some(ready.node.handle.publication_point_rsync_uri.clone()),
|
||||
..ReadyStageMetrics::default()
|
||||
};
|
||||
let mut warnings = ready.repo_outcome.warnings.clone();
|
||||
let repo_outcome = ready.repo_outcome.clone();
|
||||
let stage_fresh_started = Instant::now();
|
||||
let stage = runner.stage_fresh_publication_point_after_repo_ready(
|
||||
&ready.node.handle,
|
||||
repo_outcome.repo_sync_ok,
|
||||
repo_outcome.repo_sync_err.as_deref(),
|
||||
);
|
||||
metrics.stage_fresh_ms = elapsed_ms(stage_fresh_started);
|
||||
|
||||
let fresh_stage = match stage {
|
||||
Ok(stage) => stage,
|
||||
Err(_) => {
|
||||
Err(err) => {
|
||||
if metrics.stage_fresh_ms >= crate::progress_log::stage_fresh_slow_threshold_ms() {
|
||||
crate::progress_log::emit(
|
||||
"phase2_stage_fresh_slow",
|
||||
serde_json::json!({
|
||||
"manifest_rsync_uri": ready.node.handle.manifest_rsync_uri.as_str(),
|
||||
"publication_point_rsync_uri": ready.node.handle.publication_point_rsync_uri.as_str(),
|
||||
"status": "error",
|
||||
"error": err.error.to_string(),
|
||||
"stage_fresh_ms": metrics.stage_fresh_ms,
|
||||
"snapshot_prepare_ms": err.snapshot_prepare_ms,
|
||||
"repo_sync_source": repo_outcome.repo_sync_source.as_deref(),
|
||||
"repo_sync_phase": repo_outcome.repo_sync_phase.as_deref(),
|
||||
"repo_sync_duration_ms": repo_outcome.repo_sync_duration_ms,
|
||||
}),
|
||||
);
|
||||
}
|
||||
metrics.fallback_count = 1;
|
||||
let fallback = runner.run_publication_point(&ready.node.handle);
|
||||
if let Ok(result) = fallback.as_ref() {
|
||||
metrics.discovered_children = result.discovered_children.len();
|
||||
let child_enqueue_started = Instant::now();
|
||||
enqueue_discovered_children(
|
||||
runner,
|
||||
next_id,
|
||||
@@ -241,16 +629,73 @@ fn stage_ready_publication_point(
|
||||
&ready.node,
|
||||
result.discovered_children.clone(),
|
||||
);
|
||||
metrics.child_enqueue_ms = elapsed_ms(child_enqueue_started);
|
||||
}
|
||||
finished.push(FinishedPublicationPoint {
|
||||
node: ready.node,
|
||||
result: compact_phase2_finished_result_result(fallback, compact_audit),
|
||||
});
|
||||
return;
|
||||
metrics.total_ms = elapsed_ms(publication_point_started);
|
||||
return metrics;
|
||||
}
|
||||
};
|
||||
metrics.snapshot_prepare_ms = fresh_stage.snapshot_prepare_ms;
|
||||
metrics.snapshot_current_index_lock_ms =
|
||||
fresh_stage.snapshot_prepare_timing.current_index_lock_ms;
|
||||
metrics.snapshot_manifest_load_ms = fresh_stage.snapshot_prepare_timing.manifest_load_ms;
|
||||
metrics.snapshot_manifest_index_lookup_ms =
|
||||
fresh_stage.snapshot_prepare_timing.manifest_index_lookup_ms;
|
||||
metrics.snapshot_manifest_blob_load_ms =
|
||||
fresh_stage.snapshot_prepare_timing.manifest_blob_load_ms;
|
||||
metrics.snapshot_manifest_decode_ms = fresh_stage.snapshot_prepare_timing.manifest_decode_ms;
|
||||
metrics.snapshot_replay_guard_ms = fresh_stage.snapshot_prepare_timing.replay_guard_ms;
|
||||
metrics.replay_meta_hit_count = fresh_stage.snapshot_prepare_timing.replay_meta_hit as usize;
|
||||
metrics.replay_meta_miss_count = fresh_stage.snapshot_prepare_timing.replay_meta_miss as usize;
|
||||
metrics.snapshot_manifest_entries_ms = fresh_stage.snapshot_prepare_timing.manifest_entries_ms;
|
||||
metrics.snapshot_pack_files_ms = fresh_stage.snapshot_prepare_timing.pack_files_ms;
|
||||
metrics.snapshot_pack_files_index_lookup_ms = fresh_stage
|
||||
.snapshot_prepare_timing
|
||||
.pack_files_index_lookup_ms;
|
||||
metrics.snapshot_pack_files_blob_load_ms =
|
||||
fresh_stage.snapshot_prepare_timing.pack_files_blob_load_ms;
|
||||
metrics.snapshot_ee_path_validate_ms = fresh_stage.snapshot_prepare_timing.ee_path_validate_ms;
|
||||
metrics.snapshot_manifest_file_count = fresh_stage.snapshot_prepare_timing.manifest_file_count;
|
||||
metrics.child_discovery_ms = fresh_stage.child_discovery_ms;
|
||||
if metrics.stage_fresh_ms >= crate::progress_log::stage_fresh_slow_threshold_ms() {
|
||||
crate::progress_log::emit(
|
||||
"phase2_stage_fresh_slow",
|
||||
serde_json::json!({
|
||||
"manifest_rsync_uri": ready.node.handle.manifest_rsync_uri.as_str(),
|
||||
"publication_point_rsync_uri": ready.node.handle.publication_point_rsync_uri.as_str(),
|
||||
"status": "ok",
|
||||
"stage_fresh_ms": metrics.stage_fresh_ms,
|
||||
"snapshot_prepare_ms": fresh_stage.snapshot_prepare_ms,
|
||||
"snapshot_current_index_lock_ms": fresh_stage.snapshot_prepare_timing.current_index_lock_ms,
|
||||
"snapshot_manifest_load_ms": fresh_stage.snapshot_prepare_timing.manifest_load_ms,
|
||||
"snapshot_manifest_index_lookup_ms": fresh_stage.snapshot_prepare_timing.manifest_index_lookup_ms,
|
||||
"snapshot_manifest_blob_load_ms": fresh_stage.snapshot_prepare_timing.manifest_blob_load_ms,
|
||||
"snapshot_manifest_decode_ms": fresh_stage.snapshot_prepare_timing.manifest_decode_ms,
|
||||
"snapshot_replay_guard_ms": fresh_stage.snapshot_prepare_timing.replay_guard_ms,
|
||||
"replay_meta_hit": fresh_stage.snapshot_prepare_timing.replay_meta_hit,
|
||||
"replay_meta_miss": fresh_stage.snapshot_prepare_timing.replay_meta_miss,
|
||||
"snapshot_manifest_entries_ms": fresh_stage.snapshot_prepare_timing.manifest_entries_ms,
|
||||
"snapshot_pack_files_ms": fresh_stage.snapshot_prepare_timing.pack_files_ms,
|
||||
"snapshot_pack_files_index_lookup_ms": fresh_stage.snapshot_prepare_timing.pack_files_index_lookup_ms,
|
||||
"snapshot_pack_files_blob_load_ms": fresh_stage.snapshot_prepare_timing.pack_files_blob_load_ms,
|
||||
"snapshot_ee_path_validate_ms": fresh_stage.snapshot_prepare_timing.ee_path_validate_ms,
|
||||
"snapshot_manifest_file_count": fresh_stage.snapshot_prepare_timing.manifest_file_count,
|
||||
"child_discovery_ms": fresh_stage.child_discovery_ms,
|
||||
"child_count": fresh_stage.discovered_children.len(),
|
||||
"repo_sync_source": repo_outcome.repo_sync_source.as_deref(),
|
||||
"repo_sync_phase": repo_outcome.repo_sync_phase.as_deref(),
|
||||
"repo_sync_duration_ms": repo_outcome.repo_sync_duration_ms,
|
||||
}),
|
||||
);
|
||||
}
|
||||
warnings.extend(fresh_stage.warnings.clone());
|
||||
|
||||
metrics.discovered_children = fresh_stage.discovered_children.len();
|
||||
let child_enqueue_started = Instant::now();
|
||||
enqueue_discovered_children(
|
||||
runner,
|
||||
next_id,
|
||||
@@ -258,7 +703,9 @@ fn stage_ready_publication_point(
|
||||
&ready.node,
|
||||
fresh_stage.discovered_children.clone(),
|
||||
);
|
||||
metrics.child_enqueue_ms = elapsed_ms(child_enqueue_started);
|
||||
|
||||
let prepare_started = Instant::now();
|
||||
match prepare_publication_point_for_parallel_roa(
|
||||
ready.node.id,
|
||||
&fresh_stage.fresh_point,
|
||||
@@ -270,6 +717,10 @@ fn stage_ready_publication_point(
|
||||
runner.persist_vcir,
|
||||
) {
|
||||
ParallelObjectsPrepare::Complete(mut objects) => {
|
||||
metrics.prepare_ms = elapsed_ms(prepare_started);
|
||||
metrics.complete_count = 1;
|
||||
metrics.roa_tasks = objects.stats.roa_total;
|
||||
metrics.aspa_objects = objects.stats.aspa_total;
|
||||
objects
|
||||
.router_keys
|
||||
.extend(fresh_stage.discovered_router_keys.clone());
|
||||
@@ -291,12 +742,20 @@ fn stage_ready_publication_point(
|
||||
);
|
||||
}
|
||||
ParallelObjectsPrepare::Staged(objects_stage) => {
|
||||
metrics.prepare_ms = elapsed_ms(prepare_started);
|
||||
metrics.staged_count = 1;
|
||||
metrics.locked_files = objects_stage.locked_file_count();
|
||||
metrics.aspa_objects = objects_stage.aspa_task_count();
|
||||
let build_tasks_started = Instant::now();
|
||||
let tasks = objects_stage.build_roa_tasks();
|
||||
metrics.build_roa_tasks_ms = elapsed_ms(build_tasks_started);
|
||||
let task_count = objects_stage.roa_task_count();
|
||||
metrics.roa_tasks = task_count;
|
||||
for task in tasks {
|
||||
pending_roa_dispatch.push_back(task);
|
||||
}
|
||||
if task_count == 0 {
|
||||
metrics.zero_task_count = 1;
|
||||
match reduce_parallel_roa_stage(objects_stage, Vec::new(), runner.timing.as_ref()) {
|
||||
Ok(mut objects) => {
|
||||
objects
|
||||
@@ -336,12 +795,23 @@ fn stage_ready_publication_point(
|
||||
started_at: publication_point_started,
|
||||
objects_started_at: Instant::now(),
|
||||
task_count,
|
||||
tasks_submitted: 0,
|
||||
first_task_submitted_at: None,
|
||||
last_task_submitted_at: None,
|
||||
first_result_at: None,
|
||||
last_result_at: None,
|
||||
worker_ms_total: 0,
|
||||
worker_ms_max: 0,
|
||||
queue_wait_ms_total: 0,
|
||||
queue_wait_ms_max: 0,
|
||||
results: Vec::with_capacity(task_count),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
metrics.total_ms = elapsed_ms(publication_point_started);
|
||||
metrics
|
||||
}
|
||||
|
||||
fn enqueue_discovered_children(
|
||||
@@ -410,15 +880,32 @@ fn finalize_ready_objects(
|
||||
fn flush_pending_roa_dispatch(
|
||||
runner: &Rpkiv1PublicationPointRunner<'_>,
|
||||
pending_roa_dispatch: &mut VecDeque<OwnedRoaTask>,
|
||||
) -> Result<(), TreeRunError> {
|
||||
inflight_publication_points: &mut HashMap<u64, InflightPublicationPoint>,
|
||||
) -> Result<RoaDispatchMetrics, TreeRunError> {
|
||||
let started = Instant::now();
|
||||
let mut metrics = RoaDispatchMetrics::default();
|
||||
let Some(pool) = runner.parallel_roa_worker_pool.as_ref() else {
|
||||
return Ok(());
|
||||
return Ok(metrics);
|
||||
};
|
||||
while let Some(task) = pending_roa_dispatch.pop_front() {
|
||||
while let Some(mut task) = pending_roa_dispatch.pop_front() {
|
||||
metrics.attempted += 1;
|
||||
let pp_id = task.publication_point_id;
|
||||
task.submitted_at = Some(Instant::now());
|
||||
match pool.try_submit_round_robin(task) {
|
||||
Ok(_) => {}
|
||||
Ok(_) => {
|
||||
metrics.submitted += 1;
|
||||
if let Some(state) = inflight_publication_points.get_mut(&pp_id) {
|
||||
let now = Instant::now();
|
||||
state.tasks_submitted += 1;
|
||||
if state.first_task_submitted_at.is_none() {
|
||||
state.first_task_submitted_at = Some(now);
|
||||
}
|
||||
state.last_task_submitted_at = Some(now);
|
||||
}
|
||||
}
|
||||
Err(ObjectWorkerSubmitError::QueueFull { task, .. }) => {
|
||||
pending_roa_dispatch.push_front(task);
|
||||
metrics.queue_full = true;
|
||||
break;
|
||||
}
|
||||
Err(ObjectWorkerSubmitError::Disconnected { .. }) => {
|
||||
@@ -428,7 +915,9 @@ fn flush_pending_roa_dispatch(
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
metrics.pending_remaining = pending_roa_dispatch.len();
|
||||
metrics.duration_ms = elapsed_ms(started);
|
||||
Ok(metrics)
|
||||
}
|
||||
|
||||
fn drain_object_results(
|
||||
@@ -436,9 +925,11 @@ fn drain_object_results(
|
||||
inflight_publication_points: &mut HashMap<u64, InflightPublicationPoint>,
|
||||
finished: &mut Vec<FinishedPublicationPoint>,
|
||||
compact_audit: bool,
|
||||
) -> Result<(), TreeRunError> {
|
||||
) -> Result<ObjectDrainMetrics, TreeRunError> {
|
||||
let started = Instant::now();
|
||||
let mut metrics = ObjectDrainMetrics::default();
|
||||
let Some(pool) = runner.parallel_roa_worker_pool.as_ref() else {
|
||||
return Ok(());
|
||||
return Ok(metrics);
|
||||
};
|
||||
loop {
|
||||
let Some(result) = pool
|
||||
@@ -447,8 +938,23 @@ fn drain_object_results(
|
||||
else {
|
||||
break;
|
||||
};
|
||||
metrics.results_drained += 1;
|
||||
let pp_id = result.publication_point_id;
|
||||
let _worker_index = result.worker_index;
|
||||
metrics.worker_ms_total += result.worker_ms;
|
||||
metrics.worker_ms_max = metrics.worker_ms_max.max(result.worker_ms);
|
||||
metrics.queue_wait_ms_total += result.queue_wait_ms;
|
||||
metrics.queue_wait_ms_max = metrics.queue_wait_ms_max.max(result.queue_wait_ms);
|
||||
let should_finalize = if let Some(state) = inflight_publication_points.get_mut(&pp_id) {
|
||||
let now = Instant::now();
|
||||
if state.first_result_at.is_none() {
|
||||
state.first_result_at = Some(now);
|
||||
}
|
||||
state.last_result_at = Some(now);
|
||||
state.worker_ms_total += result.worker_ms;
|
||||
state.worker_ms_max = state.worker_ms_max.max(result.worker_ms);
|
||||
state.queue_wait_ms_total += result.queue_wait_ms;
|
||||
state.queue_wait_ms_max = state.queue_wait_ms_max.max(result.queue_wait_ms);
|
||||
state.results.push(result);
|
||||
state.results.len() == state.task_count
|
||||
} else {
|
||||
@@ -459,12 +965,19 @@ fn drain_object_results(
|
||||
.remove(&pp_id)
|
||||
.expect("inflight publication point must exist");
|
||||
let objects_processing_ms = state.objects_started_at.elapsed().as_millis() as u64;
|
||||
match reduce_parallel_roa_stage(
|
||||
let reduce_started = Instant::now();
|
||||
let reduce_result = reduce_parallel_roa_stage(
|
||||
state.objects_stage,
|
||||
state.results,
|
||||
runner.timing.as_ref(),
|
||||
) {
|
||||
);
|
||||
let reduce_ms = elapsed_ms(reduce_started);
|
||||
metrics.reduce_ms_total += reduce_ms;
|
||||
metrics.reduce_ms_max = metrics.reduce_ms_max.max(reduce_ms);
|
||||
metrics.publication_points_finalized += 1;
|
||||
match reduce_result {
|
||||
Ok(mut objects) => {
|
||||
let finalize_started = Instant::now();
|
||||
objects
|
||||
.router_keys
|
||||
.extend(state.fresh_stage.discovered_router_keys.clone());
|
||||
@@ -488,12 +1001,37 @@ fn drain_object_results(
|
||||
state.repo_outcome.repo_sync_err.as_deref(),
|
||||
)
|
||||
.map(|out| out.result);
|
||||
let finalize_ms = elapsed_ms(finalize_started);
|
||||
metrics.finalize_ms_total += finalize_ms;
|
||||
metrics.finalize_ms_max = metrics.finalize_ms_max.max(finalize_ms);
|
||||
crate::progress_log::emit(
|
||||
"phase2_publication_point_reduced",
|
||||
serde_json::json!({
|
||||
"manifest_rsync_uri": state.node.handle.manifest_rsync_uri,
|
||||
"publication_point_rsync_uri": state.node.handle.publication_point_rsync_uri,
|
||||
"manifest_rsync_uri": state.node.handle.manifest_rsync_uri.as_str(),
|
||||
"publication_point_rsync_uri": state.node.handle.publication_point_rsync_uri.as_str(),
|
||||
"objects_processing_ms": objects_processing_ms,
|
||||
"task_count": state.task_count,
|
||||
"tasks_submitted": state.tasks_submitted,
|
||||
"first_task_submitted_ms": state.first_task_submitted_at.map(|t| t.saturating_duration_since(state.objects_started_at).as_millis() as u64),
|
||||
"last_task_submitted_ms": state.last_task_submitted_at.map(|t| t.saturating_duration_since(state.objects_started_at).as_millis() as u64),
|
||||
"task_submit_span_ms": match (state.first_task_submitted_at, state.last_task_submitted_at) {
|
||||
(Some(first), Some(last)) => Some(last.saturating_duration_since(first).as_millis() as u64),
|
||||
_ => None,
|
||||
},
|
||||
"first_result_ms": state.first_result_at.map(|t| t.saturating_duration_since(state.objects_started_at).as_millis() as u64),
|
||||
"last_result_ms": state.last_result_at.map(|t| t.saturating_duration_since(state.objects_started_at).as_millis() as u64),
|
||||
"result_span_ms": match (state.first_result_at, state.last_result_at) {
|
||||
(Some(first), Some(last)) => Some(last.saturating_duration_since(first).as_millis() as u64),
|
||||
_ => None,
|
||||
},
|
||||
"worker_ms_total": state.worker_ms_total,
|
||||
"worker_ms_max": state.worker_ms_max,
|
||||
"worker_ms_avg": if state.task_count > 0 { state.worker_ms_total / state.task_count as u64 } else { 0 },
|
||||
"queue_wait_ms_total": state.queue_wait_ms_total,
|
||||
"queue_wait_ms_max": state.queue_wait_ms_max,
|
||||
"queue_wait_ms_avg": if state.task_count > 0 { state.queue_wait_ms_total / state.task_count as u64 } else { 0 },
|
||||
"reduce_ms": reduce_ms,
|
||||
"finalize_ms": finalize_ms,
|
||||
"total_duration_ms": state.started_at.elapsed().as_millis() as u64,
|
||||
}),
|
||||
);
|
||||
@@ -509,7 +1047,8 @@ fn drain_object_results(
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
metrics.duration_ms = elapsed_ms(started);
|
||||
Ok(metrics)
|
||||
}
|
||||
|
||||
fn drain_repo_events(
|
||||
@@ -517,11 +1056,15 @@ fn drain_repo_events(
|
||||
ca_waiting_repo_by_identity: &mut HashMap<RepoIdentity, Vec<QueuedCaInstance>>,
|
||||
ready_queue: &mut VecDeque<ReadyCaInstance>,
|
||||
timeout: Duration,
|
||||
) -> Result<(), TreeRunError> {
|
||||
) -> Result<RepoDrainMetrics, TreeRunError> {
|
||||
let started = Instant::now();
|
||||
let mut metrics = RepoDrainMetrics::default();
|
||||
if let Some(event) = repo_runtime
|
||||
.recv_repo_result_timeout(timeout)
|
||||
.map_err(TreeRunError::Runner)?
|
||||
{
|
||||
metrics.event_count += 1;
|
||||
metrics.completions += event.completions.len();
|
||||
for completion in event.completions {
|
||||
let mut outcome = completion.outcome;
|
||||
if completion.identity != event.transport_identity {
|
||||
@@ -530,6 +1073,7 @@ fn drain_repo_events(
|
||||
outcome.repo_sync_duration_ms = 0;
|
||||
}
|
||||
if let Some(waiters) = ca_waiting_repo_by_identity.remove(&completion.identity) {
|
||||
metrics.ready_enqueued += waiters.len();
|
||||
for node in waiters {
|
||||
ready_queue.push_back(ReadyCaInstance {
|
||||
node,
|
||||
@@ -539,7 +1083,8 @@ fn drain_repo_events(
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
metrics.duration_ms = elapsed_ms(started);
|
||||
Ok(metrics)
|
||||
}
|
||||
|
||||
fn event_poll_timeout(
|
||||
|
||||
Reference in New Issue
Block a user