#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct MemoryStats {
pub structure_bytes: u64,
pub index_bytes: u64,
pub property_bytes: u64,
pub compressed_values: u64,
pub compressed_plain_bytes: u64,
pub compressed_stored_bytes: u64,
pub compression_dictionary_bytes: u64,
}
impl MemoryStats {
pub fn total_bytes(&self) -> u64 {
self.structure_bytes
+ self.index_bytes
+ self.property_bytes
+ self.compression_dictionary_bytes
}
pub fn total_mb(&self) -> f64 {
self.total_bytes() as f64 / 1_048_576.0
}
}
pub(crate) fn compute(graph: &super::TraversalGraph) -> MemoryStats {
use super::{EdgeData, VertexData};
let (node_cap, edge_cap) = graph.graph.capacity();
let node_slot_size = std::mem::size_of::<petgraph::graph::Node<Option<VertexData>>>() as u64;
let edge_slot_size = std::mem::size_of::<petgraph::graph::Edge<Option<EdgeData>>>() as u64;
let mut structure_bytes = node_cap as u64 * node_slot_size + edge_cap as u64 * edge_slot_size;
let mut property_bytes = 0u64;
let mut compressed = CompressedCount::default();
for (id, labels, properties, _) in graph.graph.node_weights() {
if id.is_heap_allocated() {
structure_bytes += id.capacity() as u64;
}
structure_bytes +=
labels.capacity() as u64 * std::mem::size_of::<compact_str::CompactString>() as u64;
for label in labels.iter() {
if label.is_heap_allocated() {
structure_bytes += label.capacity() as u64;
}
}
property_bytes += crate::value::estimated_element_heap_bytes(properties);
if !graph.compression.is_empty() {
properties.values().for_each(|value| compressed.add(value));
}
}
for (id, edge_label, properties, _) in graph.graph.edge_weights() {
if id.is_heap_allocated() {
structure_bytes += id.capacity() as u64;
}
if let Some(label) = edge_label
&& label.is_heap_allocated()
{
structure_bytes += label.capacity() as u64;
}
if let Some(properties) = properties {
property_bytes += crate::value::estimated_element_heap_bytes(properties);
if !graph.compression.is_empty() {
properties.values().for_each(|value| compressed.add(value));
}
}
}
structure_bytes += graph.node_generations.heap_bytes() + graph.edge_generations.heap_bytes();
let index_bytes = graph.index.estimated_heap_bytes();
MemoryStats {
structure_bytes,
index_bytes,
property_bytes,
compressed_values: compressed.values,
compressed_plain_bytes: compressed.plain,
compressed_stored_bytes: compressed.stored,
compression_dictionary_bytes: graph.compression.dictionary_bytes(),
}
}
#[derive(Default)]
struct CompressedCount {
values: u64,
plain: u64,
stored: u64,
}
impl CompressedCount {
fn add(&mut self, value: &crate::value::ElementProperty) {
use crate::value::ElementProperty;
match value {
ElementProperty::Compressed(text) => {
self.values += 1;
self.plain += text.plain_len() as u64;
self.stored += text.stored_len() as u64;
}
ElementProperty::Object(map) => map.values().for_each(|value| self.add(value)),
_ => {}
}
}
}
fn string_heap(text: &compact_str::CompactString) -> u64 {
if text.is_heap_allocated() {
text.capacity() as u64
} else {
0
}
}
#[cfg_attr(not(feature = "persist"), allow(dead_code))]
pub(crate) fn loaded_vertex_bytes(
id: &compact_str::CompactString,
labels: &[compact_str::CompactString],
properties: &crate::value::PropertyMap,
) -> u64 {
use std::mem::size_of;
let slot = size_of::<petgraph::graph::Node<Option<super::VertexData>>>() as u64;
let index_entry = size_of::<(compact_str::CompactString, super::VertexHandle)>() as u64;
let mut bytes = slot + index_entry + string_heap(id);
bytes += labels.len() as u64
* (size_of::<compact_str::CompactString>() + size_of::<super::VertexHandle>()) as u64;
bytes += labels.iter().map(string_heap).sum::<u64>();
bytes + crate::value::estimated_element_heap_bytes(properties)
}
#[cfg_attr(not(feature = "persist"), allow(dead_code))]
pub(crate) fn loaded_edge_bytes(
id: &str,
label: Option<&str>,
properties: Option<&crate::value::PropertyMap>,
) -> u64 {
use std::mem::size_of;
let slot = size_of::<petgraph::graph::Edge<Option<super::EdgeData>>>() as u64;
let index_entry = size_of::<(compact_str::CompactString, super::EdgeHandle)>() as u64;
let heap = |text: &str| -> u64 {
if text.len() > 24 {
text.len() as u64
} else {
0
}
};
let mut bytes = slot + index_entry + heap(id);
if let Some(label) = label {
bytes += heap(label) + size_of::<super::EdgeHandle>() as u64;
}
bytes + properties.map_or(0, crate::value::estimated_element_heap_bytes)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct FootprintKey {
commit_seq: u64,
vertices: u64,
edges: u64,
}
#[derive(Debug, Default)]
pub(crate) struct FootprintCache {
entry: parking_lot::Mutex<Option<(FootprintKey, u64)>>,
#[cfg(test)]
pub(crate) walks: std::sync::atomic::AtomicU64,
}
pub(crate) fn footprint_bytes(graph: &super::TraversalGraph) -> u64 {
let key = FootprintKey {
commit_seq: graph.commit_seq(),
vertices: graph.graph.node_count() as u64,
edges: graph.graph.edge_count() as u64,
};
let cacheable = graph.undo.entries().is_empty();
if cacheable
&& let Some((cached, bytes)) = *graph.footprint.entry.lock()
&& cached == key
{
return bytes;
}
#[cfg(test)]
graph
.footprint
.walks
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let bytes = compute(graph).total_bytes();
if cacheable {
*graph.footprint.entry.lock() = Some((key, bytes));
}
bytes
}