use super::{
ArchiveDebugError, ArchiveDebugOptions, ArchiveDebugResult, BoundedSegmentGraph, HashMap,
HashSet, ParsedSegment, SegmentGraph, SegmentIdentifier, WorkBudget,
};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum ArchiveGraphOrigin {
Stored,
Reconstructed,
}
#[derive(Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum ArchiveGraphReferences {
Available(Vec<SegmentIdentifier>),
Unavailable {
details: String,
},
}
#[derive(Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub struct ArchiveGraphRow {
pub segment_identifier: SegmentIdentifier,
pub references: ArchiveGraphReferences,
}
#[derive(Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub struct ArchiveDebugGraph {
pub origin: ArchiveGraphOrigin,
pub rows: Vec<ArchiveGraphRow>,
}
pub(crate) fn diagnostic_archive_graph(
archive: &crate::tar_archive::TarArchiveReader,
work_budget: &mut WorkBudget,
options: ArchiveDebugOptions,
) -> ArchiveDebugResult<ArchiveDebugGraph> {
let segment_count = archive.segment_count();
check_graph_budget(options, segment_count, 0)?;
work_budget.charge_one()?;
match archive.segment_graph_with_limits(
work_budget.remaining(),
options.maximum_graph_rows,
options.maximum_graph_edges,
) {
BoundedSegmentGraph::Available { graph, work_units } => {
work_budget.charge_amount(work_units)?;
return totalize_stored_graph(archive, &graph, work_budget, options);
}
BoundedSegmentGraph::Unavailable { work_units } => {
work_budget.charge_amount(work_units)?;
}
BoundedSegmentGraph::WorkBudgetExceeded {
attempted_work_units,
} => return Err(work_budget.exceeded_by(attempted_work_units)),
BoundedSegmentGraph::GraphBudgetExceeded {
attempted_rows,
attempted_edges,
} => return Err(graph_budget_error(options, attempted_rows, attempted_edges)),
}
work_budget.charge_many(segment_count)?;
let mut rows = Vec::with_capacity(segment_count);
let mut graph_edges = 0usize;
for segment_identifier in archive.segment_identifiers() {
let references = if segment_identifier.is_data_segment() {
match archive.segment_data(segment_identifier) {
None => ArchiveGraphReferences::Unavailable {
details: "archive index does not resolve this segment's bytes".to_owned(),
},
Some(bytes) => {
work_budget.charge_many(bytes.len())?;
match ParsedSegment::validated_data_segment_reference_count(
segment_identifier,
bytes,
) {
Ok(reference_count) => {
graph_edges = graph_edges.saturating_add(reference_count);
check_graph_budget(options, segment_count, graph_edges)?;
work_budget.charge_many(reference_count)?;
match ParsedSegment::parse(segment_identifier, bytes) {
Ok(segment) => ArchiveGraphReferences::Available(
sorted_unique_segment_identifiers(segment.referenced_segments),
),
Err(error) => ArchiveGraphReferences::Unavailable {
details: error.to_string(),
},
}
}
Err(error) => ArchiveGraphReferences::Unavailable {
details: error.to_string(),
},
}
}
}
} else {
ArchiveGraphReferences::Available(Vec::new())
};
rows.push(ArchiveGraphRow {
segment_identifier,
references,
});
}
Ok(ArchiveDebugGraph {
origin: ArchiveGraphOrigin::Reconstructed,
rows,
})
}
pub(crate) fn totalize_stored_graph(
archive: &crate::tar_archive::TarArchiveReader,
stored_graph: &SegmentGraph,
work_budget: &mut WorkBudget,
options: ArchiveDebugOptions,
) -> ArchiveDebugResult<ArchiveDebugGraph> {
let mut references_by_source: HashMap<SegmentIdentifier, HashSet<SegmentIdentifier>> =
HashMap::new();
for (source, references) in &stored_graph.adjacency {
work_budget.charge_one()?;
work_budget.charge_many(references.len())?;
references_by_source.insert(*source, references.iter().copied().collect());
}
let segment_count = archive.segment_count();
check_graph_budget(options, segment_count, 0)?;
work_budget.charge_many(segment_count)?;
let mut rows = Vec::with_capacity(segment_count);
for segment_identifier in archive.segment_identifiers() {
let references = references_by_source
.remove(&segment_identifier)
.map_or_else(Vec::new, sorted_unique_segment_identifiers);
rows.push(ArchiveGraphRow {
segment_identifier,
references: ArchiveGraphReferences::Available(references),
});
}
Ok(ArchiveDebugGraph {
origin: ArchiveGraphOrigin::Stored,
rows,
})
}
pub(crate) fn check_graph_budget(
options: ArchiveDebugOptions,
attempted_rows: usize,
attempted_edges: usize,
) -> ArchiveDebugResult<()> {
if attempted_rows > options.maximum_graph_rows || attempted_edges > options.maximum_graph_edges
{
return Err(graph_budget_error(options, attempted_rows, attempted_edges));
}
Ok(())
}
pub(crate) fn graph_budget_error(
options: ArchiveDebugOptions,
attempted_rows: usize,
attempted_edges: usize,
) -> ArchiveDebugError {
ArchiveDebugError::GraphBudgetExceeded {
maximum_graph_rows: options.maximum_graph_rows,
maximum_graph_edges: options.maximum_graph_edges,
attempted_graph_rows: attempted_rows,
attempted_graph_edges: attempted_edges,
}
}
pub(crate) fn sorted_unique_segment_identifiers(
identifiers: impl IntoIterator<Item = SegmentIdentifier>,
) -> Vec<SegmentIdentifier> {
let mut unique: HashSet<SegmentIdentifier> = identifiers.into_iter().collect();
let mut identifiers: Vec<SegmentIdentifier> = unique.drain().collect();
identifiers.sort_by_key(|identifier| {
(
identifier.most_significant_bits,
identifier.least_significant_bits,
)
});
identifiers
}