///|
pub(all) struct NodePage {
items : Array[Node]
total : Int32
offset : Int32
}
///|
pub(all) struct EdgePage {
items : Array[Edge]
total : Int32
offset : Int32
}
///|
pub(all) struct Group {
kind : String
name : String
count : Int32
shallow_bytes : Int64
max_retained_bytes : Int64?
}
///|
fn page_bounds(offset : Int32, limit : Int32) -> Unit raise HeapError {
guard offset >= 0 && limit > 0 && limit <= 1000 else {
raise Invalid("offset >= 0 and limit in 1..1000 required")
}
}
///|
pub fn Analysis::nodes(
self : Analysis,
order~ : String = "retained",
query~ : String = "",
kind~ : String = "",
min_retained~ : Int64 = -1L,
min_shallow~ : Int64 = 0L,
reachability~ : String = "all",
offset~ : Int32 = 0,
limit~ : Int32 = 100,
) -> NodePage raise HeapError {
page_bounds(offset, limit)
guard ["all", "reachable", "unreachable"].contains(reachability) &&
min_retained >= -1L &&
min_shallow >= 0L else {
raise Invalid("invalid object filter")
}
guard order == "retained" || order == "shallow" || order == "index" else {
raise Invalid("unknown node sort order")
}
let found = self.graph.snapshot.nodes.filter(n => {
(query == "" || n.name.contains(query) || n.id == query) &&
(kind == "" || n.kind == kind) &&
n.self_size >= min_shallow &&
self.retained[n.index] >= min_retained &&
(
reachability == "all" ||
(reachability == "reachable" && self.retained[n.index] >= 0L) ||
(reachability == "unreachable" && self.retained[n.index] < 0L)
)
})
if order != "index" {
found.sort_by((a, b) => {
let av = if order == "retained" {
self.retained[a.index]
} else {
a.self_size
}
let bv = if order == "retained" {
self.retained[b.index]
} else {
b.self_size
}
if av > bv {
-1
} else if av < bv {
1
} else {
a.index - b.index
}
})
}
let items : Array[Node] = []
for i in 0..= offset && items.length() < limit {
items.push(found[i])
}
}
{ items, total: found.length(), offset, }
}
///|
/// Groups sum shallow bytes only. max_retained_bytes is not additive.
pub fn Analysis::groups(self : Analysis) -> Array[Group] {
let groups : Map[(String, String), Group] = Map([])
for node in self.graph.snapshot.nodes {
let key = (node.kind, node.name)
let retained = self.retained[node.index]
let r = if retained < 0L { None } else { Some(retained) }
match groups.get(key) {
None =>
groups[key] = {
kind: node.kind,
name: node.name,
count: 1,
shallow_bytes: node.self_size,
max_retained_bytes: r,
}
Some(g) => {
let max_r = match (g.max_retained_bytes, r) {
(Some(a), Some(b)) => Some(if a > b { a } else { b })
(Some(a), None) => Some(a)
(None, value) => value
}
groups[key] = {
..g,
count: g.count + 1,
shallow_bytes: g.shallow_bytes + node.self_size,
max_retained_bytes: max_r,
}
}
}
}
let result = groups.values().to_array()
result.sort_by((a, b) => {
if a.shallow_bytes > b.shallow_bytes {
-1
} else if a.shallow_bytes < b.shallow_bytes {
1
} else {
let c = a.kind.compare(b.kind)
if c != 0 {
c
} else {
a.name.compare(b.name)
}
}
})
result
}
///|
pub fn Graph::references(
self : Graph,
node : Int32,
incoming~ : Bool = true,
retaining_only~ : Bool = false,
offset~ : Int32 = 0,
limit~ : Int32 = 100,
) -> EdgePage raise HeapError {
page_bounds(offset, limit)
let n = self.snapshot.node(node)
let start = if incoming { self.incoming_offsets[node] } else { n.edge_start }
let end = if incoming {
self.incoming_offsets[node + 1]
} else {
n.edge_start + n.edge_count
}
let result : Array[Edge] = []
let mut total = 0
for cursor in start..= offset && result.length() < limit {
result.push(self.snapshot.edges[i])
}
total += 1
}
{ items: result, total, offset, }
}
///|
pub fn Analysis::dominated(
self : Analysis,
node : Int32,
offset~ : Int32 = 0,
limit~ : Int32 = 100,
) -> NodePage raise HeapError {
page_bounds(offset, limit)
ignore(self.graph.snapshot.node(node))
let children : Array[Node] = []
for p in self.dominated_offsets[node].. {
if self.retained[a.index] > self.retained[b.index] {
-1
} else if self.retained[a.index] < self.retained[b.index] {
1
} else {
a.index - b.index
}
})
let items : Array[Node] = []
for i in 0..= offset && items.length() < limit {
items.push(children[i])
}
}
{ items, total: children.length(), offset, }
}