OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 // --- | 5 // --- |
6 // Author: Sainbayar Sukhbaatar | 6 // Author: Sainbayar Sukhbaatar |
7 // Dai Mikurube | 7 // Dai Mikurube |
8 // | 8 // |
9 | 9 |
10 #include "deep-heap-profile.h" | 10 #include "deep-heap-profile.h" |
11 | 11 |
12 #ifdef DEEP_HEAP_PROFILE | 12 #ifdef DEEP_HEAP_PROFILE |
13 #include <algorithm> | 13 #include <algorithm> |
14 #include <fcntl.h> | 14 #include <fcntl.h> |
15 #include <sys/stat.h> | 15 #include <sys/stat.h> |
16 #include <sys/types.h> | 16 #include <sys/types.h> |
17 #ifdef HAVE_UNISTD_H | 17 #ifdef HAVE_UNISTD_H |
18 #include <unistd.h> // for getpagesize and getpid | 18 #include <unistd.h> // for getpagesize and getpid |
19 #endif // HAVE_UNISTD_H | 19 #endif // HAVE_UNISTD_H |
20 | 20 |
21 #include "base/cycleclock.h" | 21 #include "base/cycleclock.h" |
22 #include "base/sysinfo.h" | 22 #include "base/sysinfo.h" |
23 #include "internal_logging.h" // for ASSERT, etc | 23 #include "internal_logging.h" // for ASSERT, etc |
24 #include "memory_region_map.h" | |
25 | 24 |
26 static const int kProfilerBufferSize = 1 << 20; | 25 static const int kProfilerBufferSize = 1 << 20; |
27 static const int kHashTableSize = 179999; // Same as heap-profile-table.cc. | 26 static const int kHashTableSize = 179999; // Same as heap-profile-table.cc. |
28 | 27 |
29 static const int PAGEMAP_BYTES = 8; | 28 static const int PAGEMAP_BYTES = 8; |
30 static const uint64 MAX_ADDRESS = kuint64max; | 29 static const uint64 MAX_ADDRESS = kuint64max; |
31 | 30 |
32 // Tag strings in heap profile dumps. | 31 // Tag strings in heap profile dumps. |
33 static const char kProfileHeader[] = "heap profile: "; | 32 static const char kProfileHeader[] = "heap profile: "; |
34 static const char kProfileVersion[] = "DUMP_DEEP_6"; | 33 static const char kProfileVersion[] = "DUMP_DEEP_6"; |
(...skipping 560 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
595 void DeepHeapProfile::RegionStats::Unparse(const char* name, | 594 void DeepHeapProfile::RegionStats::Unparse(const char* name, |
596 TextBuffer* buffer) { | 595 TextBuffer* buffer) { |
597 buffer->AppendString(name, 25); | 596 buffer->AppendString(name, 25); |
598 buffer->AppendChar(' '); | 597 buffer->AppendChar(' '); |
599 buffer->AppendLong(virtual_bytes_, 12); | 598 buffer->AppendLong(virtual_bytes_, 12); |
600 buffer->AppendChar(' '); | 599 buffer->AppendChar(' '); |
601 buffer->AppendLong(committed_bytes_, 12); | 600 buffer->AppendLong(committed_bytes_, 12); |
602 buffer->AppendString("\n", 0); | 601 buffer->AppendString("\n", 0); |
603 } | 602 } |
604 | 603 |
| 604 // Snapshots all virtual memory mappging stats by merging mmap(2) records from |
| 605 // MemoryRegionMap and /proc/maps, the OS-level memory mapping information. |
| 606 // Memory regions described in /proc/maps, but which are not created by mmap, |
| 607 // are accounted as "unhooked" memory regions. |
| 608 // |
| 609 // This function assumes that every memory region created by mmap is covered |
| 610 // by VMA(s) described in /proc/maps except for http://crbug.com/189114. |
| 611 // Note that memory regions created with mmap don't align with borders of VMAs |
| 612 // in /proc/maps. In other words, a memory region by mmap can cut across many |
| 613 // VMAs. Also, of course a VMA can include many memory regions by mmap. |
| 614 // It means that the following situation happens: |
| 615 // |
| 616 // => Virtual address |
| 617 // <----- VMA #1 -----><----- VMA #2 ----->...<----- VMA #3 -----><- VMA #4 -> |
| 618 // ..< mmap #1 >.<- mmap #2 -><- mmap #3 ->...<- mmap #4 ->..<-- mmap #5 -->.. |
| 619 // |
| 620 // It can happen easily as permission can be changed by mprotect(2) for a part |
| 621 // of a memory region. A change in permission splits VMA(s). |
| 622 // |
| 623 // To deal with the situation, this function iterates over MemoryRegionMap and |
| 624 // /proc/maps independently. The iterator for MemoryRegionMap is initialized |
| 625 // at the top outside the loop for /proc/maps, and it goes forward inside the |
| 626 // loop while comparing their addresses. |
| 627 // |
605 // TODO(dmikurube): Eliminate dynamic memory allocation caused by snprintf. | 628 // TODO(dmikurube): Eliminate dynamic memory allocation caused by snprintf. |
606 void DeepHeapProfile::GlobalStats::SnapshotMaps( | 629 void DeepHeapProfile::GlobalStats::SnapshotMaps( |
607 const MemoryResidenceInfoGetterInterface* memory_residence_info_getter, | 630 const MemoryResidenceInfoGetterInterface* memory_residence_info_getter, |
608 DeepHeapProfile* deep_profile, | 631 DeepHeapProfile* deep_profile, |
609 TextBuffer* mmap_dump_buffer) { | 632 TextBuffer* mmap_dump_buffer) { |
610 MemoryRegionMap::LockHolder lock_holder; | 633 MemoryRegionMap::LockHolder lock_holder; |
611 ProcMapsIterator::Buffer procmaps_iter_buffer; | 634 ProcMapsIterator::Buffer procmaps_iter_buffer; |
612 ProcMapsIterator procmaps_iter(0, &procmaps_iter_buffer); | 635 ProcMapsIterator procmaps_iter(0, &procmaps_iter_buffer); |
613 uint64 first_address, last_address, offset; | 636 uint64 vma_start_addr, vma_last_addr, offset; |
614 int64 inode; | 637 int64 inode; |
615 char* flags; | 638 char* flags; |
616 char* filename; | 639 char* filename; |
617 enum MapsRegionType type; | 640 enum MapsRegionType type; |
618 for (int i = 0; i < NUMBER_OF_MAPS_REGION_TYPES; ++i) { | 641 for (int i = 0; i < NUMBER_OF_MAPS_REGION_TYPES; ++i) { |
619 all_[i].Initialize(); | 642 all_[i].Initialize(); |
620 unhooked_[i].Initialize(); | 643 unhooked_[i].Initialize(); |
621 } | 644 } |
622 profiled_mmap_.Initialize(); | 645 profiled_mmap_.Initialize(); |
623 | 646 |
624 MemoryRegionMap::RegionIterator mmap_iter = | 647 MemoryRegionMap::RegionIterator mmap_iter = |
625 MemoryRegionMap::BeginRegionLocked(); | 648 MemoryRegionMap::BeginRegionLocked(); |
| 649 DeepBucket* deep_bucket = GetInformationOfMemoryRegion( |
| 650 mmap_iter, memory_residence_info_getter, deep_profile); |
626 | 651 |
627 while (procmaps_iter.Next(&first_address, &last_address, | 652 while (procmaps_iter.Next(&vma_start_addr, &vma_last_addr, |
628 &flags, &offset, &inode, &filename)) { | 653 &flags, &offset, &inode, &filename)) { |
629 if (mmap_dump_buffer) { | 654 if (mmap_dump_buffer) { |
630 char buffer[1024]; | 655 char buffer[1024]; |
631 int written = procmaps_iter.FormatLine(buffer, sizeof(buffer), | 656 int written = procmaps_iter.FormatLine(buffer, sizeof(buffer), |
632 first_address, last_address, flags, | 657 vma_start_addr, vma_last_addr, |
633 offset, inode, filename, 0); | 658 flags, offset, inode, filename, 0); |
634 mmap_dump_buffer->AppendString(buffer, 0); | 659 mmap_dump_buffer->AppendString(buffer, 0); |
635 } | 660 } |
636 | 661 |
637 // 'last_address' should be the last inclusive address of the region. | 662 // 'vma_last_addr' should be the last inclusive address of the region. |
638 last_address -= 1; | 663 vma_last_addr -= 1; |
639 if (strcmp("[vsyscall]", filename) == 0) { | 664 if (strcmp("[vsyscall]", filename) == 0) { |
640 continue; // Reading pagemap will fail in [vsyscall]. | 665 continue; // Reading pagemap will fail in [vsyscall]. |
641 } | 666 } |
642 | 667 |
643 type = ABSENT; | 668 type = ABSENT; |
644 if (filename[0] == '/') { | 669 if (filename[0] == '/') { |
645 if (flags[2] == 'x') | 670 if (flags[2] == 'x') |
646 type = FILE_EXEC; | 671 type = FILE_EXEC; |
647 else | 672 else |
648 type = FILE_NONEXEC; | 673 type = FILE_NONEXEC; |
649 } else if (filename[0] == '\0' || filename[0] == '\n') { | 674 } else if (filename[0] == '\0' || filename[0] == '\n') { |
650 type = ANONYMOUS; | 675 type = ANONYMOUS; |
651 } else if (strcmp(filename, "[stack]") == 0) { | 676 } else if (strcmp(filename, "[stack]") == 0) { |
652 type = STACK; | 677 type = STACK; |
653 } else { | 678 } else { |
654 type = OTHER; | 679 type = OTHER; |
655 } | 680 } |
656 all_[type].Record( | 681 all_[type].Record( |
657 memory_residence_info_getter, first_address, last_address); | 682 memory_residence_info_getter, vma_start_addr, vma_last_addr); |
658 | 683 |
659 // TODO(dmikurube): Stop double-counting pagemap. | 684 // TODO(dmikurube): Stop double-counting pagemap. |
660 // Counts unhooked memory regions in /proc/<pid>/maps. | |
661 if (MemoryRegionMap::IsRecordingLocked()) { | 685 if (MemoryRegionMap::IsRecordingLocked()) { |
662 // It assumes that every mmap'ed region is included in one maps line. | 686 uint64 cursor = vma_start_addr; |
663 uint64 cursor = first_address; | |
664 bool first = true; | 687 bool first = true; |
665 | 688 |
| 689 // Iterates over MemoryRegionMap until the iterator moves out of the VMA. |
666 do { | 690 do { |
667 Bucket* bucket = NULL; | |
668 DeepBucket* deep_bucket = NULL; | |
669 if (!first) { | 691 if (!first) { |
670 size_t committed = deep_profile->memory_residence_info_getter_-> | |
671 CommittedSize(mmap_iter->start_addr, mmap_iter->end_addr - 1); | |
672 // TODO(dmikurube): Store a reference to the bucket in region. | |
673 Bucket* bucket = MemoryRegionMap::GetBucket( | |
674 mmap_iter->call_stack_depth, mmap_iter->call_stack); | |
675 DeepBucket* deep_bucket = NULL; | |
676 if (bucket != NULL) { | |
677 deep_bucket = deep_profile->deep_table_.Lookup( | |
678 bucket, | |
679 #if defined(TYPE_PROFILING) | |
680 NULL, // No type information for mmap'ed memory regions. | |
681 #endif | |
682 /* is_mmap */ true); | |
683 } | |
684 | |
685 if (deep_bucket != NULL) | |
686 deep_bucket->committed_size += committed; | |
687 profiled_mmap_.AddToVirtualBytes( | |
688 mmap_iter->end_addr - mmap_iter->start_addr); | |
689 profiled_mmap_.AddToCommittedBytes(committed); | |
690 | |
691 cursor = mmap_iter->end_addr; | 692 cursor = mmap_iter->end_addr; |
692 ++mmap_iter; | 693 ++mmap_iter; |
693 // Don't break here even if mmap_iter == EndRegionLocked(). | 694 // Don't break here even if mmap_iter == EndRegionLocked(). |
| 695 |
| 696 if (mmap_iter != MemoryRegionMap::EndRegionLocked()) { |
| 697 deep_bucket = GetInformationOfMemoryRegion( |
| 698 mmap_iter, memory_residence_info_getter, deep_profile); |
| 699 } |
694 } | 700 } |
695 first = false; | 701 first = false; |
696 | 702 |
697 uint64 last_address_of_unhooked; | 703 uint64 last_address_of_unhooked; |
698 // If the next mmap entry is away from the current maps line. | 704 // If the next mmap entry is away from the current VMA. |
699 if (mmap_iter == MemoryRegionMap::EndRegionLocked() || | 705 if (mmap_iter == MemoryRegionMap::EndRegionLocked() || |
700 mmap_iter->start_addr > last_address) { | 706 mmap_iter->start_addr > vma_last_addr) { |
701 last_address_of_unhooked = last_address; | 707 last_address_of_unhooked = vma_last_addr; |
702 } else { | 708 } else { |
703 last_address_of_unhooked = mmap_iter->start_addr - 1; | 709 last_address_of_unhooked = mmap_iter->start_addr - 1; |
704 } | 710 } |
705 | 711 |
706 if (last_address_of_unhooked + 1 > cursor) { | 712 if (last_address_of_unhooked + 1 > cursor) { |
707 RAW_CHECK(cursor >= first_address, | 713 RAW_CHECK(cursor >= vma_start_addr, |
708 "Wrong calculation for unhooked"); | 714 "Wrong calculation for unhooked"); |
709 RAW_CHECK(last_address_of_unhooked <= last_address, | 715 RAW_CHECK(last_address_of_unhooked <= vma_last_addr, |
710 "Wrong calculation for unhooked"); | 716 "Wrong calculation for unhooked"); |
711 uint64 committed_size = unhooked_[type].Record( | 717 uint64 committed_size = unhooked_[type].Record( |
712 memory_residence_info_getter, | 718 memory_residence_info_getter, |
713 cursor, | 719 cursor, |
714 last_address_of_unhooked); | 720 last_address_of_unhooked); |
715 if (mmap_dump_buffer) { | 721 if (mmap_dump_buffer) { |
716 mmap_dump_buffer->AppendString(" ", 0); | 722 mmap_dump_buffer->AppendString(" ", 0); |
717 mmap_dump_buffer->AppendPtr(cursor, 0); | 723 mmap_dump_buffer->AppendPtr(cursor, 0); |
718 mmap_dump_buffer->AppendString(" - ", 0); | 724 mmap_dump_buffer->AppendString(" - ", 0); |
719 mmap_dump_buffer->AppendPtr(last_address_of_unhooked + 1, 0); | 725 mmap_dump_buffer->AppendPtr(last_address_of_unhooked + 1, 0); |
720 mmap_dump_buffer->AppendString(" unhooked ", 0); | 726 mmap_dump_buffer->AppendString(" unhooked ", 0); |
721 mmap_dump_buffer->AppendString(kMapsRegionTypeDict[type], 0); | 727 mmap_dump_buffer->AppendString(kMapsRegionTypeDict[type], 0); |
722 mmap_dump_buffer->AppendString(" ", 0); | 728 mmap_dump_buffer->AppendString(" ", 0); |
723 mmap_dump_buffer->AppendInt64(committed_size, 0); | 729 mmap_dump_buffer->AppendInt64(committed_size, 0); |
724 mmap_dump_buffer->AppendString("\n", 0); | 730 mmap_dump_buffer->AppendString("\n", 0); |
725 } | 731 } |
726 cursor = last_address_of_unhooked + 1; | 732 cursor = last_address_of_unhooked + 1; |
727 } | 733 } |
728 | 734 |
729 if (mmap_iter != MemoryRegionMap::EndRegionLocked() && | 735 if (mmap_iter != MemoryRegionMap::EndRegionLocked() && |
730 mmap_iter->start_addr <= last_address && | 736 mmap_iter->start_addr <= vma_last_addr && |
731 mmap_dump_buffer) { | 737 mmap_dump_buffer) { |
732 bool trailing = mmap_iter->start_addr < first_address; | 738 bool trailing = mmap_iter->start_addr < vma_start_addr; |
733 bool continued = mmap_iter->end_addr - 1 > last_address; | 739 bool continued = mmap_iter->end_addr - 1 > vma_last_addr; |
734 mmap_dump_buffer->AppendString(trailing ? " (" : " ", 0); | 740 mmap_dump_buffer->AppendString(trailing ? " (" : " ", 0); |
735 mmap_dump_buffer->AppendPtr(mmap_iter->start_addr, 0); | 741 mmap_dump_buffer->AppendPtr(mmap_iter->start_addr, 0); |
736 mmap_dump_buffer->AppendString(trailing ? ")" : " ", 0); | 742 mmap_dump_buffer->AppendString(trailing ? ")" : " ", 0); |
737 mmap_dump_buffer->AppendString("-", 0); | 743 mmap_dump_buffer->AppendString("-", 0); |
738 mmap_dump_buffer->AppendString(continued ? "(" : " ", 0); | 744 mmap_dump_buffer->AppendString(continued ? "(" : " ", 0); |
739 mmap_dump_buffer->AppendPtr(mmap_iter->end_addr, 0); | 745 mmap_dump_buffer->AppendPtr(mmap_iter->end_addr, 0); |
740 mmap_dump_buffer->AppendString(continued ? ")" : " ", 0); | 746 mmap_dump_buffer->AppendString(continued ? ")" : " ", 0); |
741 mmap_dump_buffer->AppendString(" hooked ", 0); | 747 mmap_dump_buffer->AppendString(" hooked ", 0); |
742 mmap_dump_buffer->AppendString(kMapsRegionTypeDict[type], 0); | 748 mmap_dump_buffer->AppendString(kMapsRegionTypeDict[type], 0); |
743 mmap_dump_buffer->AppendString(" @ ", 0); | 749 mmap_dump_buffer->AppendString(" @ ", 0); |
744 if (deep_bucket != NULL) { | 750 if (deep_bucket != NULL) { |
745 mmap_dump_buffer->AppendInt(deep_bucket->id, 0); | 751 mmap_dump_buffer->AppendInt(deep_bucket->id, 0); |
746 } else { | 752 } else { |
747 mmap_dump_buffer->AppendInt(0, 0); | 753 mmap_dump_buffer->AppendInt(0, 0); |
748 } | 754 } |
749 mmap_dump_buffer->AppendString("\n", 0); | 755 mmap_dump_buffer->AppendString("\n", 0); |
750 } | 756 } |
751 } while (mmap_iter != MemoryRegionMap::EndRegionLocked() && | 757 } while (mmap_iter != MemoryRegionMap::EndRegionLocked() && |
752 mmap_iter->end_addr - 1 <= last_address); | 758 mmap_iter->end_addr - 1 <= vma_last_addr); |
753 } | 759 } |
754 } | 760 } |
755 | 761 |
756 // TODO(dmikurube): Investigate and fix http://crbug.com/189114. | 762 // TODO(dmikurube): Investigate and fix http://crbug.com/189114. |
757 // | 763 // |
758 // The total committed memory usage in all_ (from /proc/<pid>/maps) is | 764 // The total committed memory usage in all_ (from /proc/<pid>/maps) is |
759 // sometimes smaller than the sum of the committed mmap'ed addresses and | 765 // sometimes smaller than the sum of the committed mmap'ed addresses and |
760 // unhooked regions. Within our observation, the difference was only 4KB | 766 // unhooked regions. Within our observation, the difference was only 4KB |
761 // in committed usage, zero in reserved virtual addresses | 767 // in committed usage, zero in reserved virtual addresses |
762 // | 768 // |
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
850 alloc_value->bucket(), | 856 alloc_value->bucket(), |
851 #if defined(TYPE_PROFILING) | 857 #if defined(TYPE_PROFILING) |
852 LookupType(pointer), | 858 LookupType(pointer), |
853 #endif | 859 #endif |
854 /* is_mmap */ false); | 860 /* is_mmap */ false); |
855 deep_bucket->committed_size += committed; | 861 deep_bucket->committed_size += committed; |
856 deep_profile->stats_.profiled_malloc_.AddToVirtualBytes(alloc_value->bytes); | 862 deep_profile->stats_.profiled_malloc_.AddToVirtualBytes(alloc_value->bytes); |
857 deep_profile->stats_.profiled_malloc_.AddToCommittedBytes(committed); | 863 deep_profile->stats_.profiled_malloc_.AddToCommittedBytes(committed); |
858 } | 864 } |
859 | 865 |
| 866 DeepHeapProfile::DeepBucket* |
| 867 DeepHeapProfile::GlobalStats::GetInformationOfMemoryRegion( |
| 868 const MemoryRegionMap::RegionIterator& mmap_iter, |
| 869 const MemoryResidenceInfoGetterInterface* memory_residence_info_getter, |
| 870 DeepHeapProfile* deep_profile) { |
| 871 size_t committed = deep_profile->memory_residence_info_getter_-> |
| 872 CommittedSize(mmap_iter->start_addr, mmap_iter->end_addr - 1); |
| 873 |
| 874 // TODO(dmikurube): Store a reference to the bucket in region. |
| 875 Bucket* bucket = MemoryRegionMap::GetBucket( |
| 876 mmap_iter->call_stack_depth, mmap_iter->call_stack); |
| 877 DeepBucket* deep_bucket = NULL; |
| 878 if (bucket != NULL) { |
| 879 deep_bucket = deep_profile->deep_table_.Lookup( |
| 880 bucket, |
| 881 #if defined(TYPE_PROFILING) |
| 882 NULL, // No type information for memory regions by mmap. |
| 883 #endif |
| 884 /* is_mmap */ true); |
| 885 if (deep_bucket != NULL) |
| 886 deep_bucket->committed_size += committed; |
| 887 } |
| 888 |
| 889 profiled_mmap_.AddToVirtualBytes( |
| 890 mmap_iter->end_addr - mmap_iter->start_addr); |
| 891 profiled_mmap_.AddToCommittedBytes(committed); |
| 892 |
| 893 return deep_bucket; |
| 894 } |
| 895 |
860 // static | 896 // static |
861 void DeepHeapProfile::WriteProcMaps(const char* prefix, | 897 void DeepHeapProfile::WriteProcMaps(const char* prefix, |
862 int buffer_size, | 898 int buffer_size, |
863 char raw_buffer[]) { | 899 char raw_buffer[]) { |
864 char filename[100]; | 900 char filename[100]; |
865 snprintf(filename, sizeof(filename), | 901 snprintf(filename, sizeof(filename), |
866 "%s.%05d.maps", prefix, static_cast<int>(getpid())); | 902 "%s.%05d.maps", prefix, static_cast<int>(getpid())); |
867 | 903 |
868 RawFD fd = RawOpenForWriting(filename); | 904 RawFD fd = RawOpenForWriting(filename); |
869 RAW_DCHECK(fd != kIllegalRawFD, ""); | 905 RAW_DCHECK(fd != kIllegalRawFD, ""); |
(...skipping 14 matching lines...) Expand all Loading... |
884 } | 920 } |
885 | 921 |
886 DeepHeapProfile::~DeepHeapProfile() { | 922 DeepHeapProfile::~DeepHeapProfile() { |
887 } | 923 } |
888 | 924 |
889 int DeepHeapProfile::FillOrderedProfile(char raw_buffer[], int buffer_size) { | 925 int DeepHeapProfile::FillOrderedProfile(char raw_buffer[], int buffer_size) { |
890 return heap_profile_->FillOrderedProfile(raw_buffer, buffer_size); | 926 return heap_profile_->FillOrderedProfile(raw_buffer, buffer_size); |
891 } | 927 } |
892 | 928 |
893 #endif // DEEP_HEAP_PROFILE | 929 #endif // DEEP_HEAP_PROFILE |
OLD | NEW |