///|
pub fn Summary::string(self : Summary, id : UInt64) -> String? {
self.strings.get(id)
}
///|
pub fn Summary::class_name(self : Summary, id : UInt64) -> String? {
match self.class_names.get(id) {
Some(name) => self.strings.get(name)
None => None
}
}
///|
pub fn Summary::class_info(self : Summary, id : UInt64) -> ClassInfo? {
self.definitions.get(id)
}
///|
pub fn Summary::object(self : Summary, id : UInt64) -> ObjectInfo? {
self.objects.get(id)
}
///|
pub fn Summary::contains_object(self : Summary, id : UInt64) -> Bool {
self.objects.contains(id) || self.definitions.contains(id)
}
///|
pub fn Summary::object_count(self : Summary) -> Int {
self.objects.length() + self.definitions.length()
}
///|
pub fn Summary::file_size(self : Summary) -> Int {
self.data.length()
}
///|
pub fn Summary::record_count(self : Summary, tag : Int) -> Int {
self.record_counts.get(tag).unwrap_or(0)
}
///|
pub fn Summary::root_count(self : Summary) -> Int {
self.roots_data.length()
}
///|
pub fn Summary::roots(
self : Summary,
offset? : Int = 0,
limit? : Int = 100,
) -> Array[RootInfo] raise ParseError {
let end = page_end(offset, limit, self.roots_data.length())
self.roots_data[offset:end].to_owned()
}
///|
fn page_end(offset : Int, limit : Int, length : Int) -> Int raise ParseError {
if offset < 0 || offset > length || limit < 0 || limit > 100_000 {
raise Invalid(offset, "invalid page (limit must be 0..100000)")
}
offset + (if limit < length - offset { limit } else { length - offset })
}
///|
pub fn Summary::class_ids(self : Summary) -> Array[UInt64] {
let ids = self.definitions.keys().to_array()
ids.sort()
ids
}
///|
pub fn Summary::find_classes(self : Summary, name : String) -> Array[UInt64] {
self.class_ids().filter(id => self.class_name(id) == Some(name))
}
///|
/// IDs are sorted numerically for stable pagination. Zero denotes all classes.
pub fn Summary::object_ids(
self : Summary,
class_id? : UInt64 = 0UL,
offset? : Int = 0,
limit? : Int = 100,
) -> Array[UInt64] raise ParseError {
let ids = []
for id, object in self.objects {
if class_id == 0UL || object.class_id == class_id {
ids.push(id)
}
}
ids.sort()
let end = page_end(offset, limit, ids.length())
ids[offset:end].to_owned()
}
///|
fn Summary::require_object(
self : Summary,
id : UInt64,
) -> ObjectInfo raise ParseError {
match self.objects.get(id) {
Some(info) => info
None => raise Invalid(0, "object not found: \{id}")
}
}
///|
/// Most-derived class first: HPROF writes fields before inherited fields.
pub fn Summary::hierarchy(
self : Summary,
class_id : UInt64,
) -> Array[ClassInfo] raise ParseError {
let seen : Map[UInt64, Bool] = Map([])
let result = []
let mut current = class_id
while current != 0UL {
if seen.contains(current) {
raise Invalid(0, "cyclic class hierarchy")
}
seen[current] = true
let info = match self.definitions.get(current) {
Some(value) => value
None => raise Invalid(0, "missing class definition: \{current}")
}
result.push(info)
current = info.superclass_id
}
result
}
///|
pub fn Summary::instance_fields(
self : Summary,
id : UInt64,
) -> Array[FieldValue] raise ParseError {
let object = self.require_object(id)
if object.kind != Instance {
raise Invalid(0, "object is not an instance")
}
let r = Reader::{
data: self.data,
pos: object.payload_offset,
end: object.payload_offset + object.payload_bytes,
id_size: self.id_size,
}
let result = []
for cls in self.hierarchy(object.class_id) {
for field in cls.fields {
let name = match self.strings.get(field.name_id) {
Some(name) => name
None => raise Invalid(r.pos, "missing field name")
}
result.push({
declaring_class_id: cls.id,
name,
value: r.value(field.kind),
})
}
}
if r.pos != r.end {
raise Invalid(r.pos, "instance payload does not match class layout")
}
result
}
///|
pub fn Summary::static_fields(
self : Summary,
class_id : UInt64,
) -> Array[FieldValue] raise ParseError {
let cls = match self.class_info(class_id) {
Some(cls) => cls
None => raise Invalid(0, "class not found")
}
let result = []
for field in cls.statics {
let name = match self.string(field.name_id) {
Some(name) => name
None => raise Invalid(0, "missing static field name")
}
result.push({ declaring_class_id: class_id, name, value: field.value, })
}
result
}
///|
/// Returns typed array elements without materializing the complete array.
pub fn Summary::array_values(
self : Summary,
id : UInt64,
offset? : Int = 0,
limit? : Int = 100,
) -> Array[Value] raise ParseError {
let object = self.require_object(id)
if object.kind == Instance {
raise Invalid(0, "object is not an array")
}
let end = page_end(offset, limit, object.length)
let kind = if object.kind == ObjectArray { 2 } else { object.primitive_kind }
let width = type_width(kind, self.id_size)
let r = Reader::{
data: self.data,
pos: object.payload_offset + offset * width,
end: object.payload_offset + object.payload_bytes,
id_size: self.id_size,
}
let result = []
for _ in offset.. ResolvedFrame? {
let frame = match self.frames.get(id) {
Some(frame) => frame
None => return None
}
let class_name = match self.class_serials.get(frame.class_serial) {
Some(id) => self.class_name(id).unwrap_or("")
None => ""
}
Some({
id,
class_name,
method_name: self.string(frame.method_name_id).unwrap_or(""),
signature: self.string(frame.signature_id).unwrap_or(""),
source_name: self.string(frame.source_name_id).unwrap_or(""),
line_number: frame.line_number,
})
}
///|
pub fn Summary::stack_trace(
self : Summary,
serial : UInt64,
) -> Array[ResolvedFrame] raise ParseError {
let trace = match self.traces.get(serial) {
Some(trace) => trace
None => raise Invalid(0, "stack trace not found")
}
let result = []
for id in trace.frame_ids {
match self.stack_frame(id) {
Some(frame) => result.push(frame)
None => raise Invalid(0, "missing stack frame")
}
}
result
}
///|
pub fn Summary::thread(self : Summary, serial : UInt64) -> ThreadInfo? {
self.threads.get(serial)
}
///|
/// Field and array edges only. Null references are omitted.
pub fn Summary::references(
self : Summary,
id : UInt64,
limit? : Int = 100,
) -> Array[ReferenceEdge] raise ParseError {
if limit < 0 || limit > 100_000 {
raise Invalid(0, "invalid reference limit")
}
let edges : Array[ReferenceEdge] = []
if self.definitions.contains(id) {
for field in self.static_fields(id) {
if edges.length() >= limit {
break
}
if field.value.reference() is Some(target) && target != 0UL {
edges.push({ source_id: id, target_id: target, label: field.name, })
}
}
return edges
}
let object = self.require_object(id)
match object.kind {
Instance =>
for field in self.instance_fields(id) {
if edges.length() >= limit {
break
}
if field.value.reference() is Some(target) && target != 0UL {
edges.push({ source_id: id, target_id: target, label: field.name, })
}
}
ObjectArray => {
// A limit bounds inspected elements as well as output edges.
let values = self.array_values(id, limit~)
for i in 0.. ()
}
edges
}
///|
fn Summary::histogram_rows(
self : Summary,
) -> Array[HistogramRow] raise ParseError {
let rows : Map[String, HistogramRow] = Map([])
for _, object in self.objects {
let primitive = object.kind == PrimitiveArray
let key = if primitive {
"P\{object.primitive_kind}"
} else {
"C\{object.class_id}"
}
let name = if primitive {
value_type_name(object.primitive_kind) + "[]"
} else {
self.class_name(object.class_id).unwrap_or("")
}
let loader_id = match self.class_info(object.class_id) {
Some(cls) => cls.loader_id
None => 0UL
}
let old = rows.get(key)
rows[key] = {
name,
class_id: object.class_id,
loader_id,
kind: object.kind.label(),
count: old.map(row => row.count).unwrap_or(0) + 1,
payload_bytes: old.map(row => row.payload_bytes).unwrap_or(0L) +
object.payload_bytes.to_int64(),
elements: old.map(row => row.elements).unwrap_or(0L) +
object.length.to_int64(),
}
}
rows.values().to_array()
}
///|
pub fn Summary::histogram(
self : Summary,
) -> Array[HistogramRow] raise ParseError {
let result = self.histogram_rows()
result.sort_by((a, b) => {
let count = b.count.compare(a.count)
if count != 0 {
count
} else {
let name = a.name.compare(b.name)
if name != 0 {
name
} else {
a.class_id.compare(b.class_id)
}
}
})
result
}