use std::{
collections::HashMap,
io::{self, ErrorKind},
};
use crate::{reader::HprofReader, types::tags, vbyte};
use super::Pass2;
pub const INB_BLOCK: usize = 16;
#[derive(Debug, Clone, Default)]
pub struct ThreadStack {
pub thread_serial: u32,
pub thread_obj_idx: u32,
pub frames: Vec<String>,
}
#[derive(Debug, Clone, Default)]
pub struct ThreadProps {
pub name: String,
pub is_daemon: bool,
pub priority: i32,
pub thread_status: i32,
pub context_loader_addr: u64,
}
#[derive(
Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema,
)]
pub struct RecordCensus {
pub utf8_records: u64,
pub load_class_records: u64,
pub unload_class_records: u64,
pub stack_frame_records: u64,
pub stack_trace_records: u64,
pub heap_dump_segments: u64,
pub instance_dumps: u64,
pub obj_array_dumps: u64,
pub prim_array_dumps: u64,
pub class_dumps: u64,
pub gc_root_tag_counts: Vec<(u8, u64)>,
}
#[derive(
Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema,
)]
pub struct DupStrings {
pub distinct_values: u64,
pub duplicated_values: u64,
pub total_string_instances: u64,
pub approx_wasted_bytes: u64,
#[serde(default)]
pub top_duplicated: Vec<DupStringSample>,
#[serde(default)]
pub length_histogram: Vec<StrLenBucket>,
#[serde(default)]
pub length_stats: StrLenStats,
#[serde(default)]
pub top_string_holders: Vec<StringHolder>,
#[serde(default)]
pub top_by_length: Vec<DupStringSample>,
#[serde(default)]
pub char_array_waste: Option<CharArrayWaste>,
}
#[derive(
Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema,
)]
pub struct DupStringSample {
pub text: String,
pub count: u64,
pub len: u32,
pub wasted_bytes: u64,
}
#[derive(
Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema,
)]
pub struct CharArrayWasteRow {
pub array_obj_1based: usize,
pub length: u64,
pub used: u64,
pub wasted_bytes: u64,
}
#[derive(
Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema,
)]
pub struct CharArrayWaste {
pub arrays_examined: u64,
pub wasteful_arrays: u64,
pub total_wasted_bytes: u64,
pub top: Vec<CharArrayWasteRow>,
}
#[derive(
Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema,
)]
pub struct StrLenBucket {
pub upper_len: u32,
pub count: u64,
}
#[derive(
Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema,
)]
pub struct StrLenStats {
pub min: u32,
pub max: u32,
pub median: u32,
pub total: u64,
}
#[derive(
Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema,
)]
pub struct StringHolder {
pub class_name: String,
pub string_refs: u64,
}
#[derive(Clone)]
pub struct AttributionRaw {
pub container_idx: u32,
pub holder_class: String,
pub field: String,
pub container_kind: u8,
pub container_class: String,
pub elements: u64,
pub capacity: u64,
}
#[derive(Clone)]
pub struct FieldSizeRaw {
pub holder_class: String,
pub field: String,
pub pointee_indices: Vec<u32>,
}
#[derive(Clone)]
pub struct CollValuesRaw {
pub container_idx: u32,
pub kind: u8,
pub container_class: String,
pub owner: Option<String>,
pub value_indices: Vec<u32>,
}
pub struct Graph {
pub n: usize,
pub format: String,
pub file_size: u64,
pub source_name: String,
pub file_path: String,
pub id_size: u8,
pub ref_size: u8,
pub header_timestamp_ms: u64,
pub gc_root_indices: Vec<u32>,
#[allow(dead_code)]
pub gc_root_types: Vec<u8>,
pub shallow: Vec<u32>,
pub class_idx: Vec<u32>,
pub class_names: Vec<String>,
pub class_loader_id: Vec<u64>,
pub loader_labels: std::collections::HashMap<u64, String>,
pub thread_stacks: Vec<ThreadStack>,
pub thread_props: std::collections::HashMap<u32, ThreadProps>,
pub thread_local_counts: std::collections::HashMap<u32, u64>,
pub thread_local_samples: std::collections::HashMap<u32, Vec<u32>>,
pub thread_local_frame_samples: std::collections::HashMap<u32, Vec<(u32, u32)>>,
pub system_properties: Vec<(String, String)>,
pub jvm_version: Option<String>,
pub class_obj_class_idx: HashMap<u32, u32>, pub fwd_offsets: Vec<u32>,
pub fwd_targets: crate::chunkvec::ChunkU32,
pub synthetic_root_count: usize,
pub system_classloader_shallow: Option<u32>,
pub idom: Vec<u32>,
pub retained: Vec<u64>,
pub has_same_class_ancestor: crate::bitset::Bitset,
pub alloc_stack_serial: Vec<u32>,
pub alloc_frames_by_serial: Option<std::collections::HashMap<u32, Vec<String>>>,
pub record_census: RecordCensus,
pub dup_strings: Option<DupStrings>,
pub arrays_by_size: crate::report::ArraysBySize,
pub collections: crate::report::CollectionsAnalysis,
pub references: crate::report::ReferencesAnalysis,
pub reference_referent_idx: [Vec<u32>; 3],
pub collection_attribution_raw: Option<Vec<AttributionRaw>>,
pub collection_attribution_truncated: bool,
pub fields_by_size_raw: Option<Vec<FieldSizeRaw>>,
pub coll_values_raw: Option<Vec<CollValuesRaw>>,
#[allow(dead_code)]
pub direct_byte_buffer_capacity_sum: u64,
#[allow(dead_code)]
pub thread_local_null_key_count: u64,
}
#[allow(dead_code)]
pub struct InboundBuilder {
pub(crate) path: String,
pub(crate) id_size: u8,
pub(crate) n: usize,
pub(crate) id_map: Option<crate::id_map::IdMap>,
pub(crate) id_map_c: Option<(Vec<u8>, usize)>,
pub(crate) id_map_codec: crate::cvec::Codec,
pub(crate) class_addr_to_hist: HashMap<u64, u32>,
pub(crate) field_plans_dense: Vec<super::FieldPlan>,
pub(crate) in_cursors: Vec<u32>,
pub(crate) total_inb: u64,
pub(crate) synthetic_edges: Vec<(u32, u32)>,
}
impl InboundBuilder {
pub fn compress_id_map(&mut self, codec: crate::cvec::Codec) -> io::Result<()> {
self.id_map_codec = codec;
if codec == crate::cvec::Codec::None {
return Ok(());
}
if let Some(m) = self.id_map.take() {
let (blob, len) = m.compress(codec)?;
self.id_map_c = Some((blob, len));
}
Ok(())
}
pub fn build_from_fwd(
self,
fwd_offsets: Vec<u32>,
mut fwd_targets: crate::chunkvec::ChunkU32,
dfn: &[u32],
) -> io::Result<(Vec<u64>, Vec<u8>)> {
let InboundBuilder {
n,
in_cursors,
total_inb,
synthetic_edges: _,
id_map,
id_map_c,
class_addr_to_hist,
field_plans_dense,
..
} = self;
drop(id_map);
drop(id_map_c);
drop(class_addr_to_hist);
drop(field_plans_dense);
let mut inb_flat = crate::chunkvec::ChunkU32::zeroed(total_inb as usize);
if crate::trace::enabled() {
eprintln!(
"[trace-rss] inbound (fwd-transpose): total_inb={} edges, inb_flat={} MB",
total_inb,
(total_inb as usize * 4) / (1024 * 1024)
);
}
crate::trace::probe("inbound fwd-transpose: after inb_flat alloc");
let mut in_cursors = in_cursors;
let n_nodes = fwd_offsets.len().saturating_sub(1);
let mut buf: Vec<u32> = Vec::with_capacity(4096);
let mut next_fwd_free: usize = 1 << 26; #[cfg(target_os = "linux")]
let fwd_off_ptr = fwd_offsets.as_ptr();
#[cfg(target_os = "linux")]
let mut next_off_dontneed: usize = 1 << 10; for src in 0..n_nodes {
let lo = fwd_offsets[src] as usize;
let hi = fwd_offsets[src + 1] as usize;
if lo == hi {
continue; }
if lo >= next_fwd_free {
fwd_targets.free_below(lo);
next_fwd_free = ((lo >> 26) + 1) << 26; }
#[cfg(target_os = "linux")]
if src >= next_off_dontneed {
let pages_end = src & !(1024 - 1); let len = pages_end * std::mem::size_of::<u32>();
if len > 0 {
unsafe {
libc::madvise(fwd_off_ptr as *mut libc::c_void, len, libc::MADV_DONTNEED);
}
}
next_off_dontneed = pages_end + 1024; }
let targets: &[u32] = if let Some(sl) = fwd_targets.range_slice(lo, hi) {
sl
} else {
fwd_targets.copy_range(lo, hi, &mut buf);
&buf
};
for &dst in targets {
let dst = dst as usize;
inb_flat.set(in_cursors[dst] as usize, src as u32);
in_cursors[dst] += 1;
}
}
drop(fwd_offsets);
drop(fwd_targets);
crate::trace::trim();
crate::trace::probe("inbound fwd-transpose: after transpose loop");
crate::trace::probe("inbound: before Phase-4 (after fwd-transpose)");
Self::encode_phase4(n, total_inb, in_cursors, inb_flat, dfn)
}
#[allow(dead_code)]
pub fn build(self, dfn: &[u32]) -> io::Result<(Vec<u64>, Vec<u8>)> {
let InboundBuilder {
path,
id_size,
n,
id_map,
id_map_c,
id_map_codec,
class_addr_to_hist,
field_plans_dense,
mut in_cursors,
total_inb,
synthetic_edges,
..
} = self;
let id_map = match id_map {
Some(m) => m,
None => {
let (blob, len) = id_map_c.expect("id_map neither live nor compressed");
crate::id_map::IdMap::from_compressed(&blob, len, id_map_codec)?
}
};
let mut inb_flat = crate::chunkvec::ChunkU32::zeroed(total_inb as usize);
if crate::trace::enabled() {
eprintln!(
"[trace-rss] inbound 2b: total_inb={} edges, inb_flat={} MB",
total_inb,
(total_inb as usize * 4) / (1024 * 1024)
);
}
crate::trace::probe("inbound 2b: after inb_flat alloc");
{
let mut r = HprofReader::open(&path)?;
let mut scratch: Vec<u8> = Vec::with_capacity(4096);
let mut fwd_t_stub: crate::chunkvec::ChunkU32 = crate::chunkvec::ChunkU32::zeroed(0);
let mut fwd_offsets_stub: Vec<u32> = Vec::new();
loop {
let tag = match r.u1() {
Err(e) if e.kind() == ErrorKind::UnexpectedEof => break,
other => other?,
};
let _ts = r.u4()?;
let length = r.u4()? as u64;
match tag {
tags::HEAP_DUMP | tags::HEAP_DUMP_SEGMENT => {
Pass2::fill_heap_2b(
&mut r,
id_size,
length,
&id_map,
&class_addr_to_hist,
&field_plans_dense,
false,
true,
&mut fwd_t_stub,
&mut fwd_offsets_stub,
&mut inb_flat,
&mut in_cursors,
&mut scratch,
)?;
}
tags::HEAP_DUMP_END => break,
_ => {
r.skip(length)?;
}
}
}
}
drop(id_map);
drop(class_addr_to_hist);
drop(field_plans_dense);
for &(src, dst) in &synthetic_edges {
inb_flat.set(in_cursors[dst as usize] as usize, src);
in_cursors[dst as usize] += 1;
}
crate::trace::probe("inbound: before Phase-4 (after 2b scan + drops)");
Self::encode_phase4(n, total_inb, in_cursors, inb_flat, dfn)
}
fn encode_phase4(
n: usize,
total_inb: u64,
in_cursors: Vec<u32>,
mut inb_flat: crate::chunkvec::ChunkU32,
dfn: &[u32],
) -> io::Result<(Vec<u64>, Vec<u8>)> {
#[allow(clippy::redundant_locals)]
let in_cursors = in_cursors;
let mut inb_block_off: Vec<u64> = Vec::with_capacity(n / INB_BLOCK + 2);
let mut inb_data: Vec<u8> = Vec::new();
let inb_data_cap = ((total_inb as usize).saturating_mul(5) / 2).min(6 * 1024 * 1024 * 1024);
inb_data.reserve(inb_data_cap);
let mut start = 0usize;
let mut nb: Vec<u32> = Vec::new();
let mut next_free_at: usize = 1 << 26;
for i in 0..n {
let end = in_cursors[i] as usize;
if i % INB_BLOCK == 0 {
inb_block_off.push(inb_data.len() as u64);
}
let count = end - start;
if count == 0 {
vbyte::encode(0, &mut inb_data);
start = end;
if start >= next_free_at {
inb_flat.free_below(start);
next_free_at = start + (1 << 26);
}
if i == n / 2 {
crate::trace::probe("inbound Phase-4: midpoint (inb_flat+inb_data coexist)");
}
continue;
}
let mut w = 0usize;
if let Some(raw) = inb_flat.range_slice(start, end) {
nb.clear();
nb.reserve(raw.len());
for &raw_val in raw {
let node = (raw_val & 0x7fff_ffff) as usize;
let pre = dfn[node];
if pre != u32::MAX {
nb.push(pre);
w += 1;
}
}
} else {
inb_flat.copy_range(start, end, &mut nb);
for r in 0..nb.len() {
let node = (nb[r] & 0x7fff_ffff) as usize;
let pre = dfn[node];
if pre != u32::MAX {
nb[w] = pre;
w += 1;
}
}
}
let unique_end = if w <= 1 {
w
} else {
let pre_slice = &mut nb[..w];
pre_slice.sort_unstable();
let mut write = 1usize;
for read in 1..pre_slice.len() {
if pre_slice[read] != pre_slice[write - 1] {
pre_slice[write] = pre_slice[read];
write += 1;
}
}
write
};
vbyte::encode(unique_end as u32, &mut inb_data);
let mut prev: u32 = 0;
for &pre in &nb[..unique_end] {
vbyte::encode(pre - prev, &mut inb_data);
prev = pre;
}
start = end;
if start >= next_free_at {
inb_flat.free_below(start);
next_free_at = start + (1 << 26);
}
if i == n / 2 {
crate::trace::probe("inbound Phase-4: midpoint (inb_flat+inb_data coexist)");
}
}
drop(nb);
drop(inb_flat);
drop(in_cursors);
inb_block_off.push(inb_data.len() as u64);
if crate::trace::enabled() {
eprintln!(
"[trace-rss] inbound Phase-4: inb_data len={} MB cap={} MB block_off len={}",
inb_data.len() / (1024 * 1024),
inb_data.capacity() / (1024 * 1024),
inb_block_off.len()
);
}
crate::trace::probe("inbound Phase-4: after inb_data built");
Ok((inb_block_off, inb_data))
}
}