use std::path::Path;
use super::{require_sorted, writer_from_header, HeaderOverrides, Result};
use crate::writer::Compression;
pub struct RenumberOptions {
pub start_node_id: i64,
pub start_way_id: i64,
pub start_relation_id: i64,
}
#[derive(Debug, Clone)]
pub struct RenumberStats {
pub nodes_written: u64,
pub ways_written: u64,
pub relations_written: u64,
pub orphan_refs: u64,
}
impl RenumberStats {
pub fn print_summary(&self) {
let total = self.nodes_written + self.ways_written + self.relations_written;
eprintln!(
"Renumbered {total} elements: {} nodes, {} ways, {} relations",
self.nodes_written, self.ways_written, self.relations_written,
);
if self.orphan_refs > 0 {
eprintln!(
"Warning: {} orphan refs preserved with old IDs (referenced \
elements not present in input)",
self.orphan_refs,
);
}
}
}
mod pass1;
mod relations;
mod schedule;
mod stage2;
mod wire_rewrite;
use pass1::pass1_parallel_scan;
use relations::{relation_r1_collect_ids, relation_r2d_assembly};
use schedule::build_all_blob_schedules;
use stage2::stage2d_parallel_way_assembly;
pub(super) const PASS1_WORKERS: usize = 4;
pub(super) const STAGE2D_WORKERS: usize = 6;
pub(super) struct StageCounters {
pub(super) pread_ms: std::sync::atomic::AtomicU64,
pub(super) decompress_ms: std::sync::atomic::AtomicU64,
pub(super) reframe_ms: std::sync::atomic::AtomicU64,
pub(super) send_ms: std::sync::atomic::AtomicU64,
pub(super) consumer_recv_ms: std::sync::atomic::AtomicU64,
pub(super) consumer_write_ms: std::sync::atomic::AtomicU64,
pub(super) blobs: std::sync::atomic::AtomicU64,
}
impl StageCounters {
pub(super) fn new() -> Self {
Self {
pread_ms: std::sync::atomic::AtomicU64::new(0),
decompress_ms: std::sync::atomic::AtomicU64::new(0),
reframe_ms: std::sync::atomic::AtomicU64::new(0),
send_ms: std::sync::atomic::AtomicU64::new(0),
consumer_recv_ms: std::sync::atomic::AtomicU64::new(0),
consumer_write_ms: std::sync::atomic::AtomicU64::new(0),
blobs: std::sync::atomic::AtomicU64::new(0),
}
}
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
pub(super) fn emit(&self, prefix: &str) {
use std::sync::atomic::Ordering::Relaxed;
crate::debug::emit_counter(&format!("{prefix}_pread_ms"), self.pread_ms.load(Relaxed) as i64);
crate::debug::emit_counter(&format!("{prefix}_decompress_ms"), self.decompress_ms.load(Relaxed) as i64);
crate::debug::emit_counter(&format!("{prefix}_reframe_ms"), self.reframe_ms.load(Relaxed) as i64);
crate::debug::emit_counter(&format!("{prefix}_send_ms"), self.send_ms.load(Relaxed) as i64);
crate::debug::emit_counter(&format!("{prefix}_consumer_recv_ms"), self.consumer_recv_ms.load(Relaxed) as i64);
crate::debug::emit_counter(&format!("{prefix}_consumer_write_ms"), self.consumer_write_ms.load(Relaxed) as i64);
crate::debug::emit_counter(&format!("{prefix}_blobs"), self.blobs.load(Relaxed) as i64);
}
}
#[allow(clippy::too_many_lines)]
#[hotpath::measure]
pub fn renumber_external(
input: &Path,
output: &Path,
opts: &RenumberOptions,
compression: Compression,
direct_io: bool,
overrides: &HeaderOverrides,
) -> Result<RenumberStats> {
#[cfg(target_os = "linux")]
unsafe {
libc::mallopt(libc::M_ARENA_MAX, 2);
}
{
let mut header_reader = crate::blob::BlobReader::open(input, direct_io)?;
let header_blob = header_reader
.next()
.ok_or_else(|| crate::error::new_error(crate::error::ErrorKind::MissingHeader))??;
let header = header_blob.to_headerblock()?;
require_sorted(&header, input, "Input PBF")?;
super::warn_locations_on_ways_loss(&header);
}
let header = {
let mut header_reader = crate::blob::BlobReader::open(input, direct_io)?;
let header_blob = header_reader
.next()
.ok_or_else(|| crate::error::new_error(crate::error::ErrorKind::MissingHeader))??;
header_blob.to_headerblock()?
};
let effective_compression = if compression == Compression::default() {
Compression::Zlib(1)
} else {
compression
};
let mut writer = writer_from_header(output, effective_compression, &header, true, overrides, |hb| {
hb.sorted()
}, direct_io, false)?;
let output_guard = crate::path_guard::PathGuard::file(output.to_path_buf());
let mut stats = RenumberStats {
nodes_written: 0,
ways_written: 0,
relations_written: 0,
orphan_refs: 0,
};
let shared_file = std::sync::Arc::new(
std::fs::File::open(input)
.map_err(|e| format!("failed to open {}: {e}", input.display()))?,
);
crate::debug::emit_marker("RENUMBER_EXT_START");
let t_sched = std::time::Instant::now();
let (pass1_schedule, way_schedule, relation_schedule) =
build_all_blob_schedules(input)?;
#[allow(clippy::cast_possible_truncation)]
crate::debug::emit_counter("renumber_ext_schedule_ms", t_sched.elapsed().as_millis() as i64);
crate::debug::emit_marker("RENUMBER_EXT_PASS1_START");
let pass1_total_nodes: u64 = pass1_schedule.iter().map(|t| t.element_count).sum();
let max_node_id = pass1_schedule
.iter()
.map(|t| t.max_id)
.max()
.unwrap_or(0);
let mut node_id_set = crate::idset::IdSet::new();
node_id_set.pre_allocate(max_node_id);
let nodes_written_atomic = std::sync::atomic::AtomicU64::new(0);
pass1_parallel_scan(
&pass1_schedule,
opts.start_node_id,
&shared_file,
&node_id_set,
&nodes_written_atomic,
&mut writer,
)?;
stats.nodes_written += nodes_written_atomic.load(std::sync::atomic::Ordering::Relaxed);
if stats.nodes_written != pass1_total_nodes {
return Err(format!(
"pass1 node count mismatch: schedule reported {pass1_total_nodes}, \
workers wrote {}",
stats.nodes_written,
)
.into());
}
crate::debug::emit_marker("RENUMBER_EXT_PASS1_END");
let t_rank = std::time::Instant::now();
node_id_set.build_rank_index();
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
{
crate::debug::emit_counter("renumber_ext_node_rank_ms", t_rank.elapsed().as_millis() as i64);
crate::debug::emit_counter(
"renumber_ext_node_map_entries",
node_id_set.total_count() as i64,
);
}
crate::debug::emit_marker("RENUMBER_EXT_STAGE2D_START");
let mut way_id_sets: Vec<crate::idset::IdSet> = (0..STAGE2D_WORKERS)
.map(|_| crate::idset::IdSet::new())
.collect();
let stage2d_ways_atomic = std::sync::atomic::AtomicU64::new(0);
let orphan_refs_atomic = std::sync::atomic::AtomicU64::new(0);
stage2d_parallel_way_assembly(
&shared_file,
&mut writer,
&mut way_id_sets,
&way_schedule,
&node_id_set,
opts.start_node_id,
opts.start_way_id,
&stage2d_ways_atomic,
&orphan_refs_atomic,
)?;
stats.ways_written += stage2d_ways_atomic.load(std::sync::atomic::Ordering::Relaxed);
stats.orphan_refs += orphan_refs_atomic.load(std::sync::atomic::Ordering::Relaxed);
crate::debug::emit_marker("RENUMBER_EXT_STAGE2D_END");
crate::debug::emit_marker("RENUMBER_EXT_R1_START");
let t_way_merge = std::time::Instant::now();
debug_assert!(
STAGE2D_WORKERS >= 1 && !way_id_sets.is_empty(),
"STAGE2D_WORKERS must be >= 1: way_id_sets.remove(0) panics on an empty vec"
);
let mut way_id_set = way_id_sets.remove(0);
for other in way_id_sets {
way_id_set.merge(other);
}
way_id_set.build_rank_index();
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
{
crate::debug::emit_counter("renumber_ext_way_merge_rank_ms", t_way_merge.elapsed().as_millis() as i64);
crate::debug::emit_counter("renumber_ext_way_map_entries", way_id_set.total_count() as i64);
}
let mut relation_id_set = crate::idset::IdSet::new();
relation_r1_collect_ids(
&shared_file,
&relation_schedule,
&mut relation_id_set,
)?;
let t_rel_rank = std::time::Instant::now();
relation_id_set.build_rank_index();
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
{
crate::debug::emit_counter("renumber_ext_rel_rank_ms", t_rel_rank.elapsed().as_millis() as i64);
crate::debug::emit_counter("renumber_ext_relation_map_entries", relation_id_set.total_count() as i64);
}
crate::debug::emit_marker("RENUMBER_EXT_R1_END");
crate::debug::emit_marker("RENUMBER_EXT_R2D_START");
relation_r2d_assembly(
&shared_file,
&relation_schedule,
&mut writer,
&node_id_set,
opts.start_node_id,
&way_id_set,
opts.start_way_id,
&relation_id_set,
opts.start_relation_id,
&mut stats,
)?;
crate::debug::emit_marker("RENUMBER_EXT_R2D_END");
writer.flush()?;
crate::debug::emit_marker("RENUMBER_EXT_END");
output_guard.commit();
Ok(stats)
}