///|
pub struct PeerRecord {
peer : String
data : Bytes
event_indices : Array[Int32]
}
///|
pub impl ToJson for PeerRecord with fn to_json(self) {
Json::object({
"peer": Json(self.peer),
"data": Json(self.data.to_array().map(fn(b) { b.to_int() })),
"event_indices": Json(self.event_indices),
})
}
///|
pub extend PeerRecord with ToJson::{to_json}
///|
pub struct PartitionedBatch {
header : Header
microseconds : Int32?
skipped : Bool
ignored_families : Int32
diagnostics : Array[Diagnostic]
route_events : Int32
state_events : Int32
peer_table : Bool
parts : Array[PeerRecord]
} derive(ToJson)
///|
pub extend PartitionedBatch with ToJson::{to_json}
///|
fn partition_peer(asn : UInt32, ip : String) -> String {
Json::array([Json(asn), Json(ip)]).stringify()
}
///|
/// 原记录先完整解码校验,再原样拼接各 peer 的 RIB 条目,只改头长度与条目数。
/// 双向 UPDATE 与会话事件使用同一 peer 分区,重置水位不会跨分区丢失。
pub fn Decoder::partition_record(
self : Decoder,
data : Bytes,
) -> PartitionedBatch raise ParseError {
let batch = self.decode(data)
let parts = []
if batch.header.kind == 13 && [2, 4, 8, 10].contains(batch.header.subtype) {
let frame = read_frame(data)
let c = Cursor::new(frame.payload)
ignore(c.u32())
ignore(
read_prefix(c, if [2, 8].contains(batch.header.subtype) { 1 } else { 2 }),
)
let count_position = 12 + c.pos
let count = c.u16()
if count != batch.events.length() {
raise Invalid(0, "分区条目数不一致")
}
let groups : Map[String, Array[(Int32, Int32, Int32)]] = Map([])
for index = 0; index < count; index = index + 1 {
let start = 12 + c.pos
ignore(c.u16())
ignore(c.u32())
if batch.header.subtype == 8 || batch.header.subtype == 10 {
ignore(c.u32())
}
ignore(c.bytes(c.u16()))
let event = batch.events[index]
let key = partition_peer(event.peer_asn, event.peer_ip)
groups.get_or_init(key, fn() { [] }).push((index, start, 12 + c.pos))
}
c.finish()
for key, ranges in groups {
let bytes = data[:count_position].to_owned().to_array()
bytes.push((ranges.length() >> 8).to_byte())
bytes.push((ranges.length() & 255).to_byte())
let indices = []
for range in ranges {
indices.push(range.0)
for byte in data[range.1:range.2] {
bytes.push(byte)
}
}
let length = (bytes.length() - 12).reinterpret_as_uint()
for i = 0; i < 4; i = i + 1 {
bytes[8 + i] = ((length >> (24 - 8 * i)) & 255U).to_byte()
}
parts.push({
peer: key,
data: Bytes::from_array(bytes),
event_indices: indices,
})
}
} else {
let key = match batch.events.get(0) {
Some(e) => Some(partition_peer(e.peer_asn, e.peer_ip))
None =>
batch.states.get(0).map(fn(s) { partition_peer(s.peer_asn, s.peer_ip) })
}
if key is Some(peer) {
if batch.events.any(fn(e) {
partition_peer(e.peer_asn, e.peer_ip) != peer
}) ||
batch.states.any(fn(s) { partition_peer(s.peer_asn, s.peer_ip) != peer }) {
raise Invalid(0, "单条非 RIB 记录出现多个 peer")
}
parts.push({
peer,
data,
event_indices: Array::makei(batch.events.length(), fn(i) { i }),
})
}
}
{
header: batch.header,
microseconds: batch.microseconds,
skipped: batch.skipped,
ignored_families: batch.ignored_families,
diagnostics: batch.diagnostics,
route_events: batch.events.length(),
state_events: batch.states.length(),
peer_table: batch.header.kind == 13 && batch.header.subtype == 1,
parts,
}
}
///|
/// 临时帧重新解码后恢复原始记录内索引;文件、记录号和偏移保持原始档案位置。
pub fn Batch::with_event_indices(
self : Batch,
indices : Array[Int32],
) -> Batch raise ParseError {
if indices.length() != self.events.length() {
raise Invalid(0, "原始事件索引数量不一致")
}
for i = 0; i < indices.length(); i = i + 1 {
if indices[i] < 0 || (i > 0 && indices[i] <= indices[i - 1]) {
raise Invalid(0, "原始事件索引须严格递增且非负")
}
}
{
..self,
events: self.events.mapi(fn(i, event) {
{
..event,
source: event.source.map(fn(source) {
{ ..source, event_index: indices[i], }
}),
}
}),
}
}
///|
/// 分区摘要按整数路径总长和样本数合并,避免合并均值带来的舍入误差。
pub fn Analyzer::path_totals(self : Analyzer) -> (Int64, Int64) {
(self.path_sum, self.path_samples)
}
///|
pub fn Analyzer::route_count(self : Analyzer) -> Int32 {
self.routes.length()
}