///|
/// MCAP Statistics record (opcode 0x0b), distinct from timing diagnostics.
pub(all) struct FileStatistics {
message_count : UInt64
schema_count : Int
channel_count : UInt
attachment_count : UInt
metadata_count : UInt
chunk_count : UInt
start_time : UInt64
end_time : UInt64
channel_message_counts : Array[(Int, UInt64)]
} derive(Eq, @debug.Debug)
///|
pub extend FileStatistics with Eq::{equal, not_equal}
///|
pub extend FileStatistics with @debug.Debug::{to_repr}
///|
pub fn encode_file_statistics(value : FileStatistics) -> Bytes raise McapError {
let out = @buffer.Buffer::Buffer()
out.write_uint64_le(value.message_count)
put_u16(out, value.schema_count)
out.write_uint_le(value.channel_count)
out.write_uint_le(value.attachment_count)
out.write_uint_le(value.metadata_count)
out.write_uint_le(value.chunk_count)
out.write_uint64_le(value.start_time)
out.write_uint64_le(value.end_time)
let pairs = @buffer.Buffer::Buffer()
let seen : Map[Int, Bool] = Map([])
for (id, count) in value.channel_message_counts {
if seen.contains(id) {
raise Invalid("duplicate Statistics channel id")
}
seen[id] = true
put_u16(pairs, id)
pairs.write_uint64_le(count)
}
put_blob(out, pairs.to_bytes())
out.to_bytes()
}
///|
pub fn decode_file_statistics(
bytes : Bytes,
limits? : Limits = default_limits(),
) -> FileStatistics raise McapError {
let input = cursor(bytes, limits)
let message_count = input.u64()
let schema_count = input.u16()
let channel_count = input.u32()
let attachment_count = input.u32()
let metadata_count = input.u32()
let chunk_count = input.u32()
let start_time = input.u64()
let end_time = input.u64()
let pairs = cursor(input.blob(), limits)
let channel_message_counts = []
let seen : Map[Int, Bool] = Map([])
while pairs.pos < pairs.data.length() {
let id = pairs.u16()
let count = pairs.u64()
if seen.contains(id) {
raise Invalid("duplicate Statistics channel id")
}
seen[id] = true
channel_message_counts.push((id, count))
}
{
message_count,
schema_count,
channel_count,
attachment_count,
metadata_count,
chunk_count,
start_time,
end_time,
channel_message_counts,
}
}
///|
pub fn compute_file_statistics(log : Log, chunk_count : UInt) -> FileStatistics {
let counts : Map[Int, UInt64] = Map([])
let mut start_time = 0UL
let mut end_time = 0UL
for i = 0; i < log.messages.length(); i = i + 1 {
let message = log.messages[i]
if i == 0 || message.log_time < start_time {
start_time = message.log_time
}
if i == 0 || message.log_time > end_time {
end_time = message.log_time
}
counts[message.channel_id] = counts.get(message.channel_id).unwrap_or(0UL) +
1UL
}
let ids = counts.keys().to_array()
ids.sort()
{
message_count: log.messages.length().to_uint64(),
schema_count: log.schemas.length(),
channel_count: log.channels.length().to_uint(),
attachment_count: log.attachments.length().to_uint(),
metadata_count: log.metadata.length().to_uint(),
chunk_count,
start_time,
end_time,
channel_message_counts: ids.map(fn(id) { (id, counts[id]) }),
}
}
///|
pub fn validate_file_statistics(
log : Log,
value : FileStatistics,
chunk_count : UInt,
) -> Unit raise McapError {
let expected = compute_file_statistics(log, chunk_count)
if value.message_count != expected.message_count ||
value.schema_count != expected.schema_count ||
value.channel_count != expected.channel_count ||
value.attachment_count != expected.attachment_count ||
value.metadata_count != expected.metadata_count ||
value.chunk_count != expected.chunk_count ||
value.start_time != expected.start_time ||
value.end_time != expected.end_time {
raise Invalid("Statistics counts or timestamps disagree with data section")
}
if value.channel_message_counts.is_empty() {
return
}
let actual : Map[Int, UInt64] = Map([])
for (id, count) in value.channel_message_counts {
if actual.contains(id) {
raise Invalid("duplicate Statistics channel id")
}
if !log.channels.any(fn(c) { c.id == id }) {
raise Invalid("Statistics references undeclared channel")
}
actual[id] = count
}
for (id, count) in expected.channel_message_counts {
if actual.get(id) != Some(count) {
raise Invalid("Statistics channel count mismatch")
}
}
for (id, count) in value.channel_message_counts {
if !expected.channel_message_counts.any(fn(pair) { pair.0 == id }) &&
count != 0UL {
raise Invalid("Statistics counts messages on unused channel")
}
}
}