1use 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#[derive(CopyGetters, Copy, Clone, Debug, Eq, Hash, PartialEq)]
63pub struct Id {
64 #[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#[derive(CopyGetters, Copy, Clone, Debug, Eq, Hash, PartialEq)]
95pub struct Serial {
96 #[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#[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#[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 pub fn records_iter(&self) -> Records<'a> {
162 Records {
163 remaining: self.records,
164 id_size: self.header.id_size,
165 }
166 }
167}
168
169pub 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#[derive(CopyGetters, Copy, Clone)]
184pub struct Header<'a> {
185 label: &'a [u8],
186 #[get_copy = "pub"]
187 id_size: IdSize,
188 #[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 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 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), };
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
237pub 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#[derive(CopyGetters, Copy, Clone)]
307pub struct Record<'a> {
308 #[get_copy = "pub"]
310 tag: RecordTag,
311 #[get_copy = "pub"]
313 micros_since_header_ts: u32,
314 id_size: IdSize,
315 body: &'a [u8],
316}
317
318impl<'a> Record<'a> {
319 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 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 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 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 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 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#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Hash, EnumIter)]
406pub enum RecordTag {
407 Utf8,
409 LoadClass,
411 UnloadClass,
413 StackFrame,
415 StackTrace,
417 AllocSites,
419 StartThread,
421 EndThread,
423 HeapSummary,
425 HeapDump,
427 CpuSamples,
429 ControlSettings,
431 HeapDumpSegment,
433 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#[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 let (input, id) = Id::parse(input, id_size)?;
477
478 Ok(Utf8 {
479 name_id: id,
480 text: input,
481 })
482 }
483
484 pub fn text_as_str(&self) -> Result<&'a str, std::str::Utf8Error> {
486 std::str::from_utf8(self.text)
487 }
488}
489
490#[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 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#[allow(unused)]
522struct UnloadClass {
523 class_serial: Serial,
524}
525
526#[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 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 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#[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 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#[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 }
617
618#[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#[allow(unused)]
631struct EndThread {
632 thread_serial: Serial,
633}
634
635#[allow(unused)]
637struct HeapSummary {
638 total_live_bytes: u32,
639}
640
641pub 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 pub fn sub_records(&self) -> SubRecords<'a> {
661 SubRecords {
662 id_size: self.id_size,
663 remaining: self.records,
664 }
665 }
666}
667
668pub 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#[allow(unused)]
695struct CpuSamples {
696 num_samples: u32,
697 num_traces: u32,
698 }
700
701#[allow(unused)]
703struct ControlSettings {
704 bits: u32,
705 stack_trace_depth: u16,
706}
707
708#[derive(Copy, Clone, Debug)]
710pub enum LineNum {
711 Normal(u32),
713 Unknown,
714 CompiledMethod,
715 NativeMethod,
716}
717
718impl LineNum {
719 fn parse(input: &[u8]) -> nom::IResult<&[u8], Self> {
720 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), },
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 #[allow(unused)]
755 fn mode(&self) -> AllocSitesFlagsMode {
756 if self.bits & 0x001 > 0 {
758 AllocSitesFlagsMode::Incremental
759 } else {
760 AllocSitesFlagsMode::Complete
761 }
762 }
763
764 #[allow(unused)]
766 fn sorting(&self) -> AllocSitesFlagsSorting {
767 if self.bits & 0x002 > 0 {
769 AllocSitesFlagsSorting::Allocation
770 } else {
771 AllocSitesFlagsSorting::Live
772 }
773 }
774
775 #[allow(unused)]
777 fn force_gc(&self) -> bool {
778 self.bits & 0x0004 > 0
779 }
780}
781
782#[allow(unused)]
784enum AllocSitesFlagsMode {
785 Incremental,
786 Complete,
787}
788
789#[allow(unused)]
791enum AllocSitesFlagsSorting {
792 Allocation,
793 Live,
794}
795
796#[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 #[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#[allow(unused)]
833struct AllocSite {
834 is_array: ObjOrArrayType,
835 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
844pub 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
859pub 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}