// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // --- // Author: Sainbayar Sukhbaatar // Dai Mikurube // // This file contains a class DeepHeapProfile and its public function // DeepHeapProfile::DumpOrderedProfile(). The function works like // HeapProfileTable::FillOrderedProfile(), but dumps directory to files. // // DeepHeapProfile::DumpOrderedProfile() dumps more detailed information about // heap usage, which includes OS-level information such as memory residency and // type information if the type profiler is available. // // DeepHeapProfile::DumpOrderedProfile() uses data stored in HeapProfileTable. // Any code in DeepHeapProfile runs only when DumpOrderedProfile() is called. // It has overhead in dumping, but no overhead in logging. // // It currently works only on Linux including Android. It does nothing in // non-Linux environments. // Note that uint64 is used to represent addresses instead of uintptr_t, and // int is used to represent buffer sizes instead of size_t. // It's for consistency with other TCMalloc functions. ProcMapsIterator uses // uint64 for addresses, and HeapProfileTable::DumpOrderedProfile uses int // for buffer sizes. #ifndef BASE_DEEP_HEAP_PROFILE_H_ #define BASE_DEEP_HEAP_PROFILE_H_ #include "config.h" #if defined(TYPE_PROFILING) #include #endif #if defined(__linux__) || defined(_WIN32) || defined(_WIN64) #define USE_DEEP_HEAP_PROFILE 1 #endif #include "addressmap-inl.h" #include "heap-profile-table.h" #include "memory_region_map.h" class DeepHeapProfile { public: enum PageFrameType { DUMP_NO_PAGEFRAME = 0, // Dumps nothing about pageframes DUMP_PFN = 1, // Dumps only pageframe numbers (PFNs) DUMP_PAGECOUNT = 2, // Dumps PFNs and pagecounts }; // Constructs a DeepHeapProfile instance. It works as a wrapper of // HeapProfileTable. // // |heap_profile| is a pointer to HeapProfileTable. DeepHeapProfile reads // data in |heap_profile| and forwards operations to |heap_profile| if // DeepHeapProfile is not available (non-Linux). // |prefix| is a prefix of dumped file names. // |pageframe_type| means what information is dumped for pageframes. DeepHeapProfile(HeapProfileTable* heap_profile, const char* prefix, enum PageFrameType pageframe_type); ~DeepHeapProfile(); // Dumps a deep profile into |fd| with using |raw_buffer| of |buffer_size|. // // In addition, a list of buckets is dumped into a ".buckets" file in // descending order of allocated bytes. void DumpOrderedProfile(const char* reason, char raw_buffer[], int buffer_size, RawFD fd); private: #ifdef USE_DEEP_HEAP_PROFILE typedef HeapProfileTable::Stats Stats; typedef HeapProfileTable::Bucket Bucket; typedef HeapProfileTable::AllocValue AllocValue; typedef HeapProfileTable::AllocationMap AllocationMap; enum MapsRegionType { // Bytes of memory which were not recognized with /proc//maps. // This size should be 0. ABSENT, // Bytes of memory which is mapped anonymously. // Regions which contain nothing in the last column of /proc//maps. ANONYMOUS, // Bytes of memory which is mapped to a executable/non-executable file. // Regions which contain file paths in the last column of /proc//maps. FILE_EXEC, FILE_NONEXEC, // Bytes of memory which is labeled [stack] in /proc//maps. STACK, // Bytes of memory which is labeled, but not mapped to any file. // Regions which contain non-path strings in the last column of // /proc//maps. OTHER, NUMBER_OF_MAPS_REGION_TYPES }; static const char* kMapsRegionTypeDict[NUMBER_OF_MAPS_REGION_TYPES]; // Manages a buffer to keep a text to be dumped to a file. class TextBuffer { public: TextBuffer(char *raw_buffer, int size, RawFD fd) : buffer_(raw_buffer), size_(size), cursor_(0), fd_(fd) { } int Size(); int FilledBytes(); void Clear(); void Flush(); bool AppendChar(char value); bool AppendString(const char* value, int width); bool AppendInt(int value, int width, bool leading_zero); bool AppendLong(long value, int width); bool AppendUnsignedLong(unsigned long value, int width); bool AppendInt64(int64 value, int width); bool AppendBase64(uint64 value, int width); bool AppendPtr(uint64 value, int width); private: bool ForwardCursor(int appended); char *buffer_; int size_; int cursor_; RawFD fd_; DISALLOW_COPY_AND_ASSIGN(TextBuffer); }; // Defines an interface for getting info about memory residence. class MemoryResidenceInfoGetterInterface { public: virtual ~MemoryResidenceInfoGetterInterface(); // Initializes the instance. virtual void Initialize() = 0; // Returns the number of resident (including swapped) bytes of the given // memory region from |first_address| to |last_address| inclusive. virtual size_t CommittedSize(uint64 first_address, uint64 last_address, TextBuffer* buffer) const = 0; // Creates a new platform specific MemoryResidenceInfoGetterInterface. static MemoryResidenceInfoGetterInterface* Create( PageFrameType pageframe_type); virtual bool IsPageCountAvailable() const = 0; protected: MemoryResidenceInfoGetterInterface(); }; #if defined(_WIN32) || defined(_WIN64) // TODO(peria): Implement this class. class MemoryInfoGetterWindows : public MemoryResidenceInfoGetterInterface { public: MemoryInfoGetterWindows(PageFrameType) {} virtual ~MemoryInfoGetterWindows() {} virtual void Initialize(); virtual size_t CommittedSize(uint64 first_address, uint64 last_address, TextBuffer* buffer) const; virtual bool IsPageCountAvailable() const; }; #endif // defined(_WIN32) || defined(_WIN64) #if defined(__linux__) // Implements MemoryResidenceInfoGetterInterface for Linux. class MemoryInfoGetterLinux : public MemoryResidenceInfoGetterInterface { public: MemoryInfoGetterLinux(PageFrameType pageframe_type) : pageframe_type_(pageframe_type), pagemap_fd_(kIllegalRawFD), kpagecount_fd_(kIllegalRawFD) {} virtual ~MemoryInfoGetterLinux() {} // Opens /proc//pagemap and stores its file descriptor. // It keeps open while the process is running. // // Note that file descriptors need to be refreshed after fork. virtual void Initialize(); // Returns the number of resident (including swapped) bytes of the given // memory region from |first_address| to |last_address| inclusive. virtual size_t CommittedSize(uint64 first_address, uint64 last_address, TextBuffer* buffer) const; virtual bool IsPageCountAvailable() const; private: struct State { uint64 pfn; bool is_committed; // Currently, we use only this bool is_present; bool is_swapped; bool is_shared; bool is_mmap; }; uint64 ReadPageCount(uint64 pfn) const; // Seeks to the offset of the open pagemap file. // It returns true if succeeded. bool Seek(uint64 address) const; // Reads a pagemap state from the current offset. // It returns true if succeeded. bool Read(State* state, bool get_pfn) const; PageFrameType pageframe_type_; RawFD pagemap_fd_; RawFD kpagecount_fd_; }; #endif // defined(__linux__) // Contains extended information for HeapProfileTable::Bucket. These objects // are managed in a hash table (DeepBucketTable) whose key is an address of // a Bucket and other additional information. struct DeepBucket { public: void UnparseForStats(TextBuffer* buffer); void UnparseForBucketFile(TextBuffer* buffer); Bucket* bucket; #if defined(TYPE_PROFILING) const std::type_info* type; // A type of the object #endif size_t committed_size; // A resident size of this bucket bool is_mmap; // True if the bucket represents a mmap region int id; // A unique ID of the bucket bool is_logged; // True if the stracktrace is logged to a file DeepBucket* next; // A reference to the next entry in the hash table }; // Manages a hash table for DeepBucket. class DeepBucketTable { public: DeepBucketTable(int size, HeapProfileTable::Allocator alloc, HeapProfileTable::DeAllocator dealloc); ~DeepBucketTable(); // Finds a DeepBucket instance corresponding to the given |bucket|, or // creates a new DeepBucket object if it doesn't exist. DeepBucket* Lookup(Bucket* bucket, #if defined(TYPE_PROFILING) const std::type_info* type, #endif bool is_mmap); // Writes stats of the hash table to |buffer| for DumpOrderedProfile. void UnparseForStats(TextBuffer* buffer); // Writes all buckets for a bucket file with using |buffer|. void WriteForBucketFile(const char* prefix, int dump_count, char raw_buffer[], int buffer_size); // Resets 'committed_size' members in DeepBucket objects. void ResetCommittedSize(); // Resets all 'is_loggeed' flags in DeepBucket objects. void ResetIsLogged(); private: // Adds |add| to a |hash_value| for Lookup. inline static void AddToHashValue(uintptr_t add, uintptr_t* hash_value); inline static void FinishHashValue(uintptr_t* hash_value); DeepBucket** table_; size_t table_size_; HeapProfileTable::Allocator alloc_; HeapProfileTable::DeAllocator dealloc_; int bucket_id_; }; class RegionStats { public: RegionStats(): virtual_bytes_(0), committed_bytes_(0) {} ~RegionStats() {} // Initializes 'virtual_bytes_' and 'committed_bytes_'. void Initialize(); // Updates itself to contain the tallies of 'virtual_bytes' and // 'committed_bytes' in the region from |first_adress| to |last_address| // inclusive. uint64 Record( const MemoryResidenceInfoGetterInterface* memory_residence_info_getter, uint64 first_address, uint64 last_address, TextBuffer* buffer); // Writes stats of the region into |buffer| with |name|. void Unparse(const char* name, TextBuffer* buffer); size_t virtual_bytes() const { return virtual_bytes_; } size_t committed_bytes() const { return committed_bytes_; } void AddToVirtualBytes(size_t additional_virtual_bytes) { virtual_bytes_ += additional_virtual_bytes; } void AddToCommittedBytes(size_t additional_committed_bytes) { committed_bytes_ += additional_committed_bytes; } void AddAnotherRegionStat(const RegionStats& other) { virtual_bytes_ += other.virtual_bytes_; committed_bytes_ += other.committed_bytes_; } private: size_t virtual_bytes_; size_t committed_bytes_; DISALLOW_COPY_AND_ASSIGN(RegionStats); }; class GlobalStats { public: // Snapshots and calculates global stats from /proc//maps and pagemap. void SnapshotMaps( const MemoryResidenceInfoGetterInterface* memory_residence_info_getter, DeepHeapProfile* deep_profile, TextBuffer* mmap_dump_buffer); // Snapshots allocations by malloc and mmap. void SnapshotAllocations(DeepHeapProfile* deep_profile); // Writes global stats into |buffer|. void Unparse(TextBuffer* buffer); private: // Records both virtual and committed byte counts of malloc and mmap regions // as callback functions for AllocationMap::Iterate(). static void RecordAlloc(const void* pointer, AllocValue* alloc_value, DeepHeapProfile* deep_profile); DeepBucket* GetInformationOfMemoryRegion( const MemoryRegionMap::RegionIterator& mmap_iter, const MemoryResidenceInfoGetterInterface* memory_residence_info_getter, DeepHeapProfile* deep_profile); // All RegionStats members in this class contain the bytes of virtual // memory and committed memory. // TODO(dmikurube): These regions should be classified more precisely later // for more detailed analysis. RegionStats all_[NUMBER_OF_MAPS_REGION_TYPES]; RegionStats unhooked_[NUMBER_OF_MAPS_REGION_TYPES]; // Total bytes of malloc'ed regions. RegionStats profiled_malloc_; // Total bytes of mmap'ed regions. RegionStats profiled_mmap_; }; // Writes reformatted /proc//maps into a file "|prefix|..maps" // with using |raw_buffer| of |buffer_size|. static void WriteProcMaps(const char* prefix, char raw_buffer[], int buffer_size); // Appends the command line (/proc/pid/cmdline on Linux) into |buffer|. bool AppendCommandLine(TextBuffer* buffer); MemoryResidenceInfoGetterInterface* memory_residence_info_getter_; // Process ID of the last dump. This can change by fork. pid_t most_recent_pid_; GlobalStats stats_; // Stats about total memory. int dump_count_; // The number of dumps. char* filename_prefix_; // Output file prefix. char run_id_[128]; DeepBucketTable deep_table_; enum PageFrameType pageframe_type_; #endif // USE_DEEP_HEAP_PROFILE HeapProfileTable* heap_profile_; DISALLOW_COPY_AND_ASSIGN(DeepHeapProfile); }; #endif // BASE_DEEP_HEAP_PROFILE_H_