jvm_hprof/
lib.rs

1//! A library for parsing hprof files.
2//!
3//! See [parse_hprof] to get started, or see the examples in the repo.
4//!
5//! # Examples
6//!
7//! Iterating across all records to count how many of each record type there are (adapted from
8//! the `analyze_hprof` example's `record-counts` subcommand):
9//!
10//! ```
11//! use std::{fs, collections};
12//! use jvm_hprof::{Hprof, parse_hprof, RecordTag, EnumIterable};
13//!
14//! fn count_records(file: fs::File) {
15//!     let memmap = unsafe { memmap::MmapOptions::new().map(&file) }.unwrap();
16//!
17//!     let hprof: Hprof = parse_hprof(&memmap[..]).unwrap();
18//!
19//!     // start with zero counts for all types
20//!     let mut counts = RecordTag::iter()
21//!         .map(|r| (r, 0_u64))
22//!         .collect::<collections::HashMap<RecordTag, u64>>();
23//!
24//!     // overwrite zeros with real counts for each record that exists in the hprof
25//!     hprof
26//!         .records_iter()
27//!         .map(|r| r.unwrap().tag())
28//!         .for_each(|tag| {
29//!             counts.entry(tag).and_modify(|c| *c += 1).or_insert(1);
30//!         });
31//!
32//!     let mut counts: Vec<(RecordTag, u64)> = counts
33//!         .into_iter()
34//!         .collect::<Vec<(jvm_hprof::RecordTag, u64)>>();
35//!
36//!     // highest count on top
37//!     counts.sort_unstable_by_key(|&(_, count)| count);
38//!     counts.reverse();
39//!
40//!     for (tag, count) in counts {
41//!         println!("{:?}: {}", tag, count);
42//!     }
43//! }
44//! ```
45use getset::CopyGetters;
46use nom::bytes::complete as bytes;
47use nom::number::complete as number;
48use std::cmp::Ordering;
49use std::fmt::{Error, Formatter};
50use std::{cmp, fmt};
51use strum_macros;
52use strum_macros::EnumIter;
53
54pub mod heap_dump;
55mod parsing_iterator;
56
57use parsing_iterator::*;
58
59/// Ids are used to identify many things in an hprof file: objects, classes, utf8 blobs, etc.
60///
61/// The on-disk representation of an Id depends on the relevant [IdSize].
62#[derive(CopyGetters, Copy, Clone, Debug, Eq, Hash, PartialEq)]
63pub struct Id {
64    // inflate 4-byte ids to 8-byte since if we have a small 32-bit heap, no worries about memory anyway
65    #[get_copy = "pub"]
66    id: u64,
67}
68
69impl From<u64> for Id {
70    fn from(id: u64) -> Id {
71        Id { id }
72    }
73}
74
75impl fmt::Display for Id {
76    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
77        write!(f, "{}", self.id)
78    }
79}
80
81impl fmt::UpperHex for Id {
82    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
83        fmt::UpperHex::fmt(&self.id, f)
84    }
85}
86
87/// An alternate means of identification used in parallel with [Id].
88///
89/// [LoadClass], for instance, has both a `class_obj_id` and a `serial`. In certain cases you might
90/// need to use one or the other. If you were processing [StackFrame] records and wanted to print
91/// human readable class names (which are available in [LoadClass], [StackFrame] uses
92/// `class_serial`, whereas if you were inspecting a class's fields via [crate::heap_dump::Class],
93/// that only has the class's `obj_id` available.
94#[derive(CopyGetters, Copy, Clone, Debug, Eq, Hash, PartialEq)]
95pub struct Serial {
96    /// The plain serial number.
97    #[get_copy = "pub"]
98    num: u32,
99}
100
101impl From<u32> for Serial {
102    fn from(num: u32) -> Self {
103        Serial { num }
104    }
105}
106
107impl fmt::Display for Serial {
108    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
109        write!(f, "{}", self.num)
110    }
111}
112
113impl fmt::UpperHex for Serial {
114    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
115        fmt::UpperHex::fmt(&self.num, f)
116    }
117}
118
119impl StatelessParserWithId for Id {
120    fn parse(input: &[u8], id_size: IdSize) -> nom::IResult<&[u8], Self> {
121        let (input, id) = match id_size {
122            IdSize::U32 => number::be_u32(input).map(|(i, id)| (i, id as u64))?,
123            IdSize::U64 => number::be_u64(input)?,
124        };
125
126        Ok((input, Id::from(id)))
127    }
128}
129
130/// Hprof ids can be 32 or 64 bit, depending on the system and JVM that the hprof was captured on.
131///
132/// This controls how ids are parsed, and can generally be otherwise ignored.
133#[derive(Debug, Clone, Copy)]
134pub enum IdSize {
135    U32,
136    U64,
137}
138
139impl IdSize {
140    fn size_in_bytes(&self) -> usize {
141        match self {
142            IdSize::U32 => 4,
143            IdSize::U64 => 8,
144        }
145    }
146}
147
148/// The top level of data loaded from an .hprof file.
149// https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp
150#[derive(CopyGetters)]
151pub struct Hprof<'a> {
152    #[get_copy = "pub"]
153    header: Header<'a>,
154    records: &'a [u8],
155}
156
157impl<'a> Hprof<'a> {
158    /// Iterate over the [Record] data in the hprof.
159    ///
160    /// Iteration is cheap, as each [Record] defers parsing the bulk of its data until later.
161    pub fn records_iter(&self) -> Records<'a> {
162        Records {
163            remaining: self.records,
164            id_size: self.header.id_size,
165        }
166    }
167}
168
169/// Entry point for parsing.
170///
171/// This is intended to be used with a memory mapped hprof file, or, for small heap dumps that can
172/// fit comfortably in memory, just an in-memory buffer.
173pub fn parse_hprof(input: &[u8]) -> ParseResult<Hprof> {
174    let (input, header) = Header::parse(input)?;
175
176    Ok(Hprof {
177        header,
178        records: input,
179    })
180}
181
182/// Basic metadata about the hprof
183#[derive(CopyGetters, Copy, Clone)]
184pub struct Header<'a> {
185    label: &'a [u8],
186    #[get_copy = "pub"]
187    id_size: IdSize,
188    /// The timestamp for the hprof as the number of millis since epoch
189    #[get_copy = "pub"]
190    timestamp_millis: u64,
191}
192
193impl<'a> Header<'a> {
194    pub fn label(&self) -> Result<&'a str, std::str::Utf8Error> {
195        std::str::from_utf8(self.label)
196    }
197
198    fn parse(input: &[u8]) -> nom::IResult<&[u8], Header> {
199        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L63
200        let (input, label) = bytes::take_until(&b"\0"[..])(input)?;
201        let (input, _) = bytes::take_while_m_n(1, 1, |b| b == 0)(input)?;
202
203        // TODO confirm endianness
204        let (input, id_size_num) = number::be_u32(input)?;
205        let (input, epoch_hi) = number::be_u32(input)?;
206        let (input, epoch_lo) = number::be_u32(input)?;
207
208        let epoch_timestamp = ((epoch_hi as u64) << 32) + (epoch_lo as u64);
209
210        let id_size = match id_size_num {
211            4 => IdSize::U32,
212            8 => IdSize::U64,
213            _ => panic!("unexpected size {}", id_size_num), // TODO
214        };
215
216        Ok((
217            input,
218            Header {
219                label,
220                id_size,
221                timestamp_millis: epoch_timestamp,
222            },
223        ))
224    }
225}
226
227impl<'a> fmt::Debug for Header<'a> {
228    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
229        f.debug_struct("Header")
230            .field("label", &self.label())
231            .field("timestamp_millis", &self.timestamp_millis())
232            .field("id_size", &self.id_size())
233            .finish()
234    }
235}
236
237/// Iterator over the [Record] data in an hprof.
238pub struct Records<'a> {
239    remaining: &'a [u8],
240    id_size: IdSize,
241}
242
243impl<'a> Iterator for Records<'a> {
244    type Item = ParseResult<'a, Record<'a>>;
245
246    fn next(&mut self) -> Option<Self::Item> {
247        if self.remaining.is_empty() {
248            return None;
249        }
250
251        let res = Record::parse(self.remaining, self.id_size);
252        match res {
253            Ok((input, record)) => {
254                self.remaining = input;
255                Some(Ok(record))
256            }
257            Err(e) => Some(Err(e)),
258        }
259    }
260}
261
262/// The next level down from the [Hprof] in the hierarchy of data.
263///
264/// See [RecordTag] for the different types of data that can be in a Record.
265///
266/// # Performance
267///
268/// Records are generic and lightweight: the parsing done to get a Record is just loading a couple
269/// of bytes to get the tag and timestamp, and a slice that contains the "body" of the record, so
270/// iterating across all Records without inspecting the body of each one is cheap. Even huge heap
271/// dumps might have only tens of thousands of Records.
272///
273/// Since iterating records is very fast but parsing the contents may not be (e.g. a sequence of
274/// 2GiB [HeapDumpSegment] records), records are especially amenable to parallel processing, e.g.
275/// with rayon's `par_bridge()`.
276///
277/// # Examples
278///
279/// Because enum variants in Rust can't (yet) have methods that exist only for one variant, parsing
280/// the body of the record is done via `as_*` methods like `as_utf_8()`. If the tag of a given
281/// record is `[RecordTag::Utf8]`, then `as_utf_8()` will return `Some(...)`, and all other `as_...`
282/// will return `None`, and so forth. So, in practice, this might look like:
283///
284/// ```
285/// use jvm_hprof::{Record, RecordTag, Utf8};
286///
287/// fn print_utf8(record: &Record) {
288///     match record.tag() {
289///         RecordTag::Utf8 => {
290///             // unwrap strips the Option(),
291///             // which we know is Some because of the tag
292///             let utf8: Utf8 = record.as_utf_8().unwrap()
293///                 // apply real error handling as needed
294///                 .expect("parsing error -- corrupt hprof? Bug in parser?");
295///
296///             // build a str from the bytes, validating UTF-8
297///             let utf_str: &str = utf8.text_as_str()
298///                 .expect("Surely the JVM wouldn't write a Utf8 record with invalid UTF-8");
299///
300///             println!("utf8 contents: {}", utf_str);
301///         }
302///         _ => println!("tag was {:?}, not utf8", record.tag())
303///     }
304/// }
305/// ```
306#[derive(CopyGetters, Copy, Clone)]
307pub struct Record<'a> {
308    /// The tag, which determines which of the `as_*` methods it is suitable to call.
309    #[get_copy = "pub"]
310    tag: RecordTag,
311    /// Microseconds since the timestamp in the header
312    #[get_copy = "pub"]
313    micros_since_header_ts: u32,
314    id_size: IdSize,
315    body: &'a [u8],
316}
317
318impl<'a> Record<'a> {
319    /// Returns `Some` if the tag is [RecordTag::Utf8] and `None` otherwise.
320    pub fn as_utf_8(&self) -> Option<ParseResult<Utf8<'a>>> {
321        match self.tag {
322            RecordTag::Utf8 => Some(Utf8::parse(self.body, self.id_size)),
323            _ => None,
324        }
325    }
326
327    /// Returns `Some` if the tag is [RecordTag::LoadClass] and `None` otherwise.
328    pub fn as_load_class(&self) -> Option<ParseResult<LoadClass>> {
329        match self.tag {
330            RecordTag::LoadClass => Some(LoadClass::parse(self.body, self.id_size)),
331            _ => None,
332        }
333    }
334
335    /// Returns `Some` if the tag is [RecordTag::StackFrame] and `None` otherwise.
336    pub fn as_stack_frame(&self) -> Option<ParseResult<StackFrame>> {
337        match self.tag {
338            RecordTag::StackFrame => Some(StackFrame::parse(self.body, self.id_size)),
339            _ => None,
340        }
341    }
342
343    /// Returns `Some` if the tag is [RecordTag::StackTrace] and `None` otherwise.
344    pub fn as_stack_trace(&self) -> Option<ParseResult<StackTrace<'a>>> {
345        match self.tag {
346            RecordTag::StackTrace => Some(StackTrace::parse(self.body, self.id_size)),
347            _ => None,
348        }
349    }
350
351    /// Returns `Some` if the tag is [RecordTag::HeapDump] or [RecordTag::HeapDumpSegment] and
352    /// `None` otherwise.
353    pub fn as_heap_dump_segment(&self) -> Option<ParseResult<HeapDumpSegment<'a>>> {
354        match self.tag {
355            RecordTag::HeapDump | RecordTag::HeapDumpSegment => {
356                Some(HeapDumpSegment::parse(self.body, self.id_size))
357            }
358            _ => None,
359        }
360    }
361
362    fn parse(input: &[u8], id_size: IdSize) -> nom::IResult<&[u8], Record> {
363        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L76
364        let (input, tag_byte) = bytes::take(1_usize)(input)?;
365
366        let tag = match tag_byte[0] {
367            0x01 => RecordTag::Utf8,
368            0x02 => RecordTag::LoadClass,
369            0x03 => RecordTag::UnloadClass,
370            0x04 => RecordTag::StackFrame,
371            0x05 => RecordTag::StackTrace,
372            0x06 => RecordTag::AllocSites,
373            0x07 => RecordTag::HeapSummary,
374            0x0A => RecordTag::StartThread,
375            0x0B => RecordTag::EndThread,
376            0x0C => RecordTag::HeapDump,
377            0x0D => RecordTag::CpuSamples,
378            0x0E => RecordTag::ControlSettings,
379            0x1C => RecordTag::HeapDumpSegment,
380            0x2C => RecordTag::HeapDumpEnd,
381            _ => panic!("unexpected tag: {:#X?}", tag_byte[0]),
382        };
383
384        let (input, micros) = number::be_u32(input)?;
385        let (input, len) = number::be_u32(input)?;
386        let (input, body) = bytes::take(len)(input)?;
387
388        Ok((
389            input,
390            Record {
391                tag,
392                micros_since_header_ts: micros,
393                id_size,
394                body,
395            },
396        ))
397    }
398}
399
400/// Indicates what type of data is contained in a particular [Record].
401///
402/// Each variant has a matching struct. Calling [Record::as_utf_8] on a `Record` with tag
403/// `RecordTag::Utf8`, for instance, will produce a [Utf8] struct.
404// Since this enum has no data, add EnumIter to allow enumerating across the variants
405#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Hash, EnumIter)]
406pub enum RecordTag {
407    /// See [Utf8]
408    Utf8,
409    /// See [LoadClass]
410    LoadClass,
411    /// Unused?
412    UnloadClass,
413    /// See [StackFrame]
414    StackFrame,
415    /// See [StackTrace]
416    StackTrace,
417    /// Unused?
418    AllocSites,
419    /// Unused?
420    StartThread,
421    /// Unused?
422    EndThread,
423    /// Unused?
424    HeapSummary,
425    /// See [HeapDumpSegment]
426    HeapDump,
427    /// Unused?
428    CpuSamples,
429    /// Unused?
430    ControlSettings,
431    /// See [HeapDumpSegment]
432    HeapDumpSegment,
433    /// Denotes the end of a heap dump
434    HeapDumpEnd,
435}
436
437impl RecordTag {
438    fn tag_byte(&self) -> u8 {
439        match self {
440            RecordTag::Utf8 => 0x01,
441            RecordTag::LoadClass => 0x02,
442            RecordTag::UnloadClass => 0x03,
443            RecordTag::StackFrame => 0x04,
444            RecordTag::StackTrace => 0x05,
445            RecordTag::AllocSites => 0x06,
446            RecordTag::HeapSummary => 0x07,
447            RecordTag::StartThread => 0x0A,
448            RecordTag::EndThread => 0x0B,
449            RecordTag::HeapDump => 0x0C,
450            RecordTag::CpuSamples => 0x0D,
451            RecordTag::ControlSettings => 0x0E,
452            RecordTag::HeapDumpSegment => 0x1C,
453            RecordTag::HeapDumpEnd => 0x2C,
454        }
455    }
456}
457
458impl cmp::Ord for RecordTag {
459    fn cmp(&self, other: &Self) -> Ordering {
460        self.tag_byte().cmp(&other.tag_byte())
461    }
462}
463
464/// Contents of a [Record] with tag [RecordTag::Utf8].
465#[derive(CopyGetters, Copy, Clone)]
466pub struct Utf8<'a> {
467    #[get_copy = "pub"]
468    name_id: Id,
469    #[get_copy = "pub"]
470    text: &'a [u8],
471}
472
473impl<'a> Utf8<'a> {
474    fn parse(input: &[u8], id_size: IdSize) -> ParseResult<Utf8> {
475        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L88
476        let (input, id) = Id::parse(input, id_size)?;
477
478        Ok(Utf8 {
479            name_id: id,
480            text: input,
481        })
482    }
483
484    /// Note that in practice, there are nonzero Utf8 records with invalid UTF-8 bytes.
485    pub fn text_as_str(&self) -> Result<&'a str, std::str::Utf8Error> {
486        std::str::from_utf8(self.text)
487    }
488}
489
490/// Contents of a [Record] with tag [RecordTag::LoadClass].
491#[derive(CopyGetters, Copy, Clone)]
492pub struct LoadClass {
493    #[get_copy = "pub"]
494    class_serial: Serial,
495    #[get_copy = "pub"]
496    class_obj_id: Id,
497    #[get_copy = "pub"]
498    stack_trace_serial: Serial,
499    #[get_copy = "pub"]
500    class_name_id: Id,
501}
502
503impl LoadClass {
504    fn parse(input: &[u8], id_size: IdSize) -> ParseResult<LoadClass> {
505        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L93
506        let (input, class_serial) = number::be_u32(input)?;
507        let (input, class_obj_id) = Id::parse(input, id_size)?;
508        let (input, stack_trace_serial) = number::be_u32(input)?;
509        let (_input, class_name_id) = Id::parse(input, id_size)?;
510
511        Ok(LoadClass {
512            class_serial: class_serial.into(),
513            class_obj_id,
514            stack_trace_serial: stack_trace_serial.into(),
515            class_name_id,
516        })
517    }
518}
519
520// TODO referenced in heapDumper.cpp, but not actually written?
521#[allow(unused)]
522struct UnloadClass {
523    class_serial: Serial,
524}
525
526/// Contents of a [Record] with tag [RecordTag::StackFrame].
527#[derive(CopyGetters, Clone)]
528pub struct StackFrame {
529    #[get_copy = "pub"]
530    id: Id,
531    #[get_copy = "pub"]
532    method_name_id: Id,
533    #[get_copy = "pub"]
534    method_signature_id: Id,
535    #[get_copy = "pub"]
536    source_file_name_id: Id,
537    #[get_copy = "pub"]
538    class_serial: Serial,
539    #[get_copy = "pub"]
540    line_num: LineNum,
541}
542
543impl StackFrame {
544    fn parse(input: &[u8], id_size: IdSize) -> ParseResult<Self> {
545        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L104
546        let (input, id) = Id::parse(input, id_size)?;
547        let (input, method_name_id) = Id::parse(input, id_size)?;
548        let (input, method_signature_id) = Id::parse(input, id_size)?;
549        // TODO Option?
550        let (input, source_file_name_id) = Id::parse(input, id_size)?;
551        let (input, class_serial) = number::be_u32(input)?;
552        let (_input, line_num) = LineNum::parse(input)?;
553
554        Ok(StackFrame {
555            id,
556            method_name_id,
557            method_signature_id,
558            source_file_name_id,
559            class_serial: class_serial.into(),
560            line_num,
561        })
562    }
563}
564
565/// Contents of a [Record] with tag [RecordTag::StackTrace].
566#[derive(CopyGetters, Clone)]
567pub struct StackTrace<'a> {
568    id_size: IdSize,
569    #[get_copy = "pub"]
570    stack_trace_serial: Serial,
571    #[get_copy = "pub"]
572    thread_serial: Serial,
573    num_frame_ids: u32,
574    frame_ids: &'a [u8],
575}
576
577impl<'a> StackTrace<'a> {
578    fn parse(input: &[u8], id_size: crate::IdSize) -> ParseResult<StackTrace> {
579        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L116
580        let (input, stack_trace_serial) = number::be_u32(input)?;
581        let (input, thread_serial) = number::be_u32(input)?;
582        let (input, num_frame_ids) = number::be_u32(input)?;
583
584        Ok(StackTrace {
585            id_size,
586            stack_trace_serial: stack_trace_serial.into(),
587            thread_serial: thread_serial.into(),
588            num_frame_ids,
589            frame_ids: input,
590        })
591    }
592
593    pub fn frame_ids(&self) -> Ids {
594        Ids {
595            iter: ParsingIterator::new_stateless_id_size(
596                self.id_size,
597                self.frame_ids,
598                self.num_frame_ids,
599            ),
600        }
601    }
602}
603
604/// Heap allocation sites, obtained after GC
605// TODO referenced in heapDumper.cpp, but not actually written?
606#[allow(unused)]
607struct AllocSites {
608    flags: AllocSitesFlags,
609    cutoff_ratio: u32,
610    total_live_bytes: u32,
611    total_live_instances: u32,
612    total_bytes_allocated: u64,
613    total_instances_allocated: u64,
614    // num_sites: u4
615    // TODO iterator over following AllocSite instances
616}
617
618// TODO referenced in heapDumper.cpp, but not actually written?
619#[allow(unused)]
620struct StartThread {
621    thread_serial: Serial,
622    thread_id: Id,
623    stack_trace_serial: Serial,
624    thread_name_id: Id,
625    thread_group_name_id: Id,
626    thread_group_parent_name_id: Id,
627}
628
629// TODO referenced in heapDumper.cpp, but not actually written?
630#[allow(unused)]
631struct EndThread {
632    thread_serial: Serial,
633}
634
635// TODO referenced in heapDumper.cpp, but not actually written?
636#[allow(unused)]
637struct HeapSummary {
638    total_live_bytes: u32,
639}
640
641/// Contents of a [Record] with tag [RecordTag::HeapDump] or [RecordTag::HeapDumpSegment].
642///
643/// Contains many [heap_dump::SubRecord]s.
644///
645/// See the [heap_dump] module.
646pub struct HeapDumpSegment<'a> {
647    id_size: IdSize,
648    records: &'a [u8],
649}
650
651impl<'a> HeapDumpSegment<'a> {
652    fn parse(input: &[u8], id_size: IdSize) -> ParseResult<HeapDumpSegment> {
653        Ok(HeapDumpSegment {
654            id_size,
655            records: input,
656        })
657    }
658
659    /// Iterate over the [heap_dump::SubRecord]s in this [Record].
660    pub fn sub_records(&self) -> SubRecords<'a> {
661        SubRecords {
662            id_size: self.id_size,
663            remaining: self.records,
664        }
665    }
666}
667
668/// Iterator over [heap_dump::SubRecord] data.
669pub struct SubRecords<'a> {
670    id_size: IdSize,
671    remaining: &'a [u8],
672}
673
674impl<'a> Iterator for SubRecords<'a> {
675    type Item = ParseResult<'a, heap_dump::SubRecord<'a>>;
676
677    fn next(&mut self) -> Option<Self::Item> {
678        if self.remaining.is_empty() {
679            return None;
680        }
681
682        let res = heap_dump::SubRecord::parse(self.remaining, self.id_size);
683        match res {
684            Ok((input, record)) => {
685                self.remaining = input;
686                Some(Ok(record))
687            }
688            Err(e) => Some(Err(e)),
689        }
690    }
691}
692
693// TODO referenced in heapDumper.cpp, but not actually written?
694#[allow(unused)]
695struct CpuSamples {
696    num_samples: u32,
697    num_traces: u32,
698    // TODO iterator over samples
699}
700
701// TODO referenced in heapDumper.cpp, but not actually written?
702#[allow(unused)]
703struct ControlSettings {
704    bits: u32,
705    stack_trace_depth: u16,
706}
707
708/// A line referenced from a stack frame.
709#[derive(Copy, Clone, Debug)]
710pub enum LineNum {
711    /// A line in a source file
712    Normal(u32),
713    Unknown,
714    CompiledMethod,
715    NativeMethod,
716}
717
718impl LineNum {
719    fn parse(input: &[u8]) -> nom::IResult<&[u8], Self> {
720        // https://github.com/openjdk/jdk/blob/08822b4e0526fe001c39fe08e241b849eddf481d/src/hotspot/share/services/heapDumper.cpp#L111
721        let (input, num) = number::be_i32(input)?;
722
723        Ok((
724            input,
725            match num {
726                num if num > 0 => LineNum::Normal(num as u32),
727                -1 => LineNum::Unknown,
728                -2 => LineNum::CompiledMethod,
729                -3 => LineNum::NativeMethod,
730                _ => panic!("Invalid line num {}", num), // TODO
731            },
732        ))
733    }
734}
735
736impl fmt::Display for LineNum {
737    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
738        match self {
739            LineNum::Normal(n) => write!(f, "{}", n),
740            LineNum::Unknown => write!(f, "Unknown"),
741            LineNum::CompiledMethod => write!(f, "CompiledMethod"),
742            LineNum::NativeMethod => write!(f, "NativeMethod"),
743        }
744    }
745}
746
747#[derive(Copy, Clone, Debug)]
748struct AllocSitesFlags {
749    bits: u16,
750}
751
752impl AllocSitesFlags {
753    // TODO referenced in heapDumper.cpp, but not actually written?
754    #[allow(unused)]
755    fn mode(&self) -> AllocSitesFlagsMode {
756        // TODO naming, correctness?
757        if self.bits & 0x001 > 0 {
758            AllocSitesFlagsMode::Incremental
759        } else {
760            AllocSitesFlagsMode::Complete
761        }
762    }
763
764    // TODO referenced in heapDumper.cpp, but not actually written?
765    #[allow(unused)]
766    fn sorting(&self) -> AllocSitesFlagsSorting {
767        // TODO
768        if self.bits & 0x002 > 0 {
769            AllocSitesFlagsSorting::Allocation
770        } else {
771            AllocSitesFlagsSorting::Live
772        }
773    }
774
775    // TODO referenced in heapDumper.cpp, but not actually written?
776    #[allow(unused)]
777    fn force_gc(&self) -> bool {
778        self.bits & 0x0004 > 0
779    }
780}
781
782// TODO referenced in heapDumper.cpp, but not actually written?
783#[allow(unused)]
784enum AllocSitesFlagsMode {
785    Incremental,
786    Complete,
787}
788
789// TODO referenced in heapDumper.cpp, but not actually written?
790#[allow(unused)]
791enum AllocSitesFlagsSorting {
792    Allocation,
793    Live,
794}
795
796// TODO referenced in heapDumper.cpp, but not actually written?
797#[allow(unused)]
798enum ObjOrArrayType {
799    Object,
800    ObjectArray,
801    BooleanArray,
802    CharArray,
803    FloatArray,
804    DoubleArray,
805    ByteArray,
806    ShortArray,
807    IntArray,
808    LongArray,
809}
810
811impl ObjOrArrayType {
812    // TODO referenced in heapDumper.cpp, but not actually written?
813    #[allow(unused)]
814    fn from_num(num: u8) -> ObjOrArrayType {
815        match num {
816            0 => ObjOrArrayType::Object,
817            2 => ObjOrArrayType::ObjectArray,
818            4 => ObjOrArrayType::BooleanArray,
819            5 => ObjOrArrayType::CharArray,
820            6 => ObjOrArrayType::FloatArray,
821            7 => ObjOrArrayType::DoubleArray,
822            8 => ObjOrArrayType::ByteArray,
823            9 => ObjOrArrayType::ShortArray,
824            10 => ObjOrArrayType::IntArray,
825            11 => ObjOrArrayType::LongArray,
826            _ => panic!("Unknown type num {}", num),
827        }
828    }
829}
830
831// TODO referenced in heapDumper.cpp, but not actually written?
832#[allow(unused)]
833struct AllocSite {
834    is_array: ObjOrArrayType,
835    /// May be zero during startup
836    class_serial: Serial,
837    stack_trace_serial: Serial,
838    num_bytes_alive: u32,
839    num_instances_alive: u32,
840    num_bytes_allocated: u32,
841    num_instances_allocated: u32,
842}
843
844/// Iterator that parses ids.
845pub struct Ids<'a> {
846    iter: ParsingIterator<'a, Id, IdSizeParserWrapper<Id>>,
847}
848
849impl<'a> Iterator for Ids<'a> {
850    type Item = ParseResult<'a, Id>;
851
852    fn next(&mut self) -> Option<Self::Item> {
853        self.iter.next()
854    }
855}
856
857type ParseResult<'e, T> = Result<T, nom::Err<(&'e [u8], nom::error::ErrorKind)>>;
858
859/// Allow iterating over enum variants for enums that have `#[derive(EnumIter)]`.
860///
861/// Wrapper around `strum`'s `IntoEnumIter` so that users don't need to know about `strum`
862pub trait EnumIterable {
863    type Iterator: Iterator<Item = Self>;
864
865    fn iter() -> Self::Iterator;
866}
867
868impl<T: strum::IntoEnumIterator> EnumIterable for T {
869    type Iterator = T::Iterator;
870
871    fn iter() -> Self::Iterator {
872        T::iter()
873    }
874}