use super::{ArchiveDebugError, ArchiveDebugResult, ArchivePathReference};
pub const DEFAULT_MAXIMUM_ARCHIVE_PATH_REFERENCES: usize = 250_000;
pub const DEFAULT_MAXIMUM_ARCHIVE_REFERENCE_TEXT_BYTES: usize = 64 * 1024 * 1024;
pub const DEFAULT_MAXIMUM_ARCHIVE_WORK_UNITS: u64 = 100_000_000;
pub const DEFAULT_MAXIMUM_ARCHIVE_SCHEDULED_CHILDREN_PER_NODE: u64 = 250_000;
pub const DEFAULT_MAXIMUM_ARCHIVE_NAME_BYTES_PER_NODE: u64 = 16 * 1024 * 1024;
pub const DEFAULT_MAXIMUM_ARCHIVE_PENDING_NODES: u64 = 250_000;
pub const DEFAULT_MAXIMUM_ARCHIVE_GRAPH_ROWS: usize = 250_000;
pub const DEFAULT_MAXIMUM_ARCHIVE_GRAPH_EDGES: usize = 1_000_000;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub struct ArchiveDebugOptions {
pub maximum_path_references: usize,
pub maximum_reference_text_bytes: usize,
pub maximum_work_units: u64,
pub maximum_scheduled_children_per_node: u64,
pub maximum_name_bytes_per_node: u64,
pub maximum_pending_nodes: u64,
pub maximum_graph_rows: usize,
pub maximum_graph_edges: usize,
}
impl Default for ArchiveDebugOptions {
fn default() -> Self {
Self {
maximum_path_references: DEFAULT_MAXIMUM_ARCHIVE_PATH_REFERENCES,
maximum_reference_text_bytes: DEFAULT_MAXIMUM_ARCHIVE_REFERENCE_TEXT_BYTES,
maximum_work_units: DEFAULT_MAXIMUM_ARCHIVE_WORK_UNITS,
maximum_scheduled_children_per_node:
DEFAULT_MAXIMUM_ARCHIVE_SCHEDULED_CHILDREN_PER_NODE,
maximum_name_bytes_per_node: DEFAULT_MAXIMUM_ARCHIVE_NAME_BYTES_PER_NODE,
maximum_pending_nodes: DEFAULT_MAXIMUM_ARCHIVE_PENDING_NODES,
maximum_graph_rows: DEFAULT_MAXIMUM_ARCHIVE_GRAPH_ROWS,
maximum_graph_edges: DEFAULT_MAXIMUM_ARCHIVE_GRAPH_EDGES,
}
}
}
pub(crate) struct WorkBudget {
pub(crate) maximum: u64,
pub(crate) consumed: u64,
}
impl WorkBudget {
pub(crate) const fn new(maximum: u64) -> Self {
Self {
maximum,
consumed: 0,
}
}
pub(crate) fn charge_one(&mut self) -> ArchiveDebugResult<()> {
self.charge_amount(1)
}
pub(crate) const fn remaining(&self) -> u64 {
self.maximum.saturating_sub(self.consumed)
}
pub(crate) fn exceeded_by(&self, units: u64) -> ArchiveDebugError {
ArchiveDebugError::WorkBudgetExceeded {
maximum_work_units: self.maximum,
attempted_work_units: self.consumed.saturating_add(units),
}
}
pub(crate) fn charge_amount(&mut self, units: u64) -> ArchiveDebugResult<()> {
let attempted = self.consumed.saturating_add(units);
if attempted > self.maximum {
return Err(self.exceeded_by(units));
}
self.consumed = attempted;
Ok(())
}
pub(crate) fn charge_many(&mut self, units: usize) -> ArchiveDebugResult<()> {
self.charge_amount(u64::try_from(units).unwrap_or(u64::MAX))
}
}
pub(crate) struct ResultBudget {
pub(crate) options: ArchiveDebugOptions,
pub(crate) retained_path_references: usize,
pub(crate) retained_reference_text_bytes: usize,
}
impl ResultBudget {
pub(crate) fn new(options: ArchiveDebugOptions) -> Self {
Self {
options,
retained_path_references: 0,
retained_reference_text_bytes: 0,
}
}
pub(crate) fn retain(&mut self, reference: &ArchivePathReference) -> ArchiveDebugResult<()> {
let attempted_path_references = self.retained_path_references.saturating_add(1);
let attempted_reference_text_bytes = self
.retained_reference_text_bytes
.saturating_add(reference.retained_text_bytes());
if attempted_path_references > self.options.maximum_path_references
|| attempted_reference_text_bytes > self.options.maximum_reference_text_bytes
{
return Err(ArchiveDebugError::ResultBudgetExceeded {
maximum_path_references: self.options.maximum_path_references,
maximum_reference_text_bytes: self.options.maximum_reference_text_bytes,
attempted_path_references,
attempted_reference_text_bytes,
});
}
self.retained_path_references = attempted_path_references;
self.retained_reference_text_bytes = attempted_reference_text_bytes;
Ok(())
}
pub(crate) fn candidate_display_budget(
&self,
base_text_bytes: usize,
) -> ArchiveDebugResult<DisplayBudget> {
let budget = DisplayBudget {
maximum_path_references: self.options.maximum_path_references,
maximum_reference_text_bytes: self.options.maximum_reference_text_bytes,
attempted_path_references: 1,
base_reference_text_bytes: base_text_bytes,
};
budget.check_display_bytes(0)?;
Ok(budget)
}
}
#[derive(Clone, Copy)]
pub(crate) struct DisplayBudget {
pub(crate) maximum_path_references: usize,
pub(crate) maximum_reference_text_bytes: usize,
pub(crate) attempted_path_references: usize,
pub(crate) base_reference_text_bytes: usize,
}
impl DisplayBudget {
pub(crate) fn check_display_bytes(self, display_bytes: usize) -> ArchiveDebugResult<()> {
let attempted_reference_text_bytes =
self.base_reference_text_bytes.saturating_add(display_bytes);
if self.attempted_path_references > self.maximum_path_references
|| attempted_reference_text_bytes > self.maximum_reference_text_bytes
{
return Err(ArchiveDebugError::ResultBudgetExceeded {
maximum_path_references: self.maximum_path_references,
maximum_reference_text_bytes: self.maximum_reference_text_bytes,
attempted_path_references: self.attempted_path_references,
attempted_reference_text_bytes,
});
}
Ok(())
}
pub(crate) fn builder(self) -> BoundedDisplay {
BoundedDisplay {
text: String::new(),
budget: self,
}
}
}
pub(crate) struct BoundedDisplay {
pub(crate) text: String,
pub(crate) budget: DisplayBudget,
}
impl BoundedDisplay {
pub(crate) fn push_str(&mut self, text: &str) -> ArchiveDebugResult<()> {
let attempted = self.text.len().saturating_add(text.len());
self.budget.check_display_bytes(attempted)?;
self.text.push_str(text);
Ok(())
}
pub(crate) fn push_char(&mut self, character: char) -> ArchiveDebugResult<()> {
let attempted = self.text.len().saturating_add(character.len_utf8());
self.budget.check_display_bytes(attempted)?;
self.text.push(character);
Ok(())
}
pub(crate) fn into_string(self) -> String {
self.text
}
}