| // Copyright 2016 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. | |
| #include "base/debug/activity_analyzer.h" | |
| #include <algorithm> | |
| #include <utility> | |
| #include "base/files/file.h" | |
| #include "base/files/file_path.h" | |
| #include "base/files/memory_mapped_file.h" | |
| #include "base/lazy_instance.h" | |
| #include "base/logging.h" | |
| #include "base/memory/ptr_util.h" | |
| #include "base/metrics/histogram_macros.h" | |
| #include "base/stl_util.h" | |
| #include "base/strings/string_util.h" | |
| namespace base { | |
| namespace debug { | |
| namespace { | |
| // An empty snapshot that can be returned when there otherwise is none. | |
| LazyInstance<ActivityUserData::Snapshot>::Leaky g_empty_user_data_snapshot; | |
| // DO NOT CHANGE VALUES. This is logged persistently in a histogram. | |
| enum AnalyzerCreationError { | |
| kInvalidMemoryMappedFile, | |
| kPmaBadFile, | |
| kPmaUninitialized, | |
| kPmaDeleted, | |
| kPmaCorrupt, | |
| kAnalyzerCreationErrorMax // Keep this last. | |
| }; | |
| void LogAnalyzerCreationError(AnalyzerCreationError error) { | |
| UMA_HISTOGRAM_ENUMERATION("ActivityTracker.Collect.AnalyzerCreationError", | |
| error, kAnalyzerCreationErrorMax); | |
| } | |
| } // namespace | |
| ThreadActivityAnalyzer::Snapshot::Snapshot() = default; | |
| ThreadActivityAnalyzer::Snapshot::~Snapshot() = default; | |
| ThreadActivityAnalyzer::ThreadActivityAnalyzer( | |
| const ThreadActivityTracker& tracker) | |
| : activity_snapshot_valid_(tracker.CreateSnapshot(&activity_snapshot_)) {} | |
| ThreadActivityAnalyzer::ThreadActivityAnalyzer(void* base, size_t size) | |
| : ThreadActivityAnalyzer(ThreadActivityTracker(base, size)) {} | |
| ThreadActivityAnalyzer::ThreadActivityAnalyzer( | |
| PersistentMemoryAllocator* allocator, | |
| PersistentMemoryAllocator::Reference reference) | |
| : ThreadActivityAnalyzer(allocator->GetAsArray<char>( | |
| reference, | |
| GlobalActivityTracker::kTypeIdActivityTracker, | |
| PersistentMemoryAllocator::kSizeAny), | |
| allocator->GetAllocSize(reference)) {} | |
| ThreadActivityAnalyzer::~ThreadActivityAnalyzer() = default; | |
| void ThreadActivityAnalyzer::AddGlobalInformation( | |
| GlobalActivityAnalyzer* global) { | |
| if (!IsValid()) | |
| return; | |
| // User-data is held at the global scope even though it's referenced at the | |
| // thread scope. | |
| activity_snapshot_.user_data_stack.clear(); | |
| for (auto& activity : activity_snapshot_.activity_stack) { | |
| // The global GetUserDataSnapshot will return an empty snapshot if the ref | |
| // or id is not valid. | |
| activity_snapshot_.user_data_stack.push_back(global->GetUserDataSnapshot( | |
| activity_snapshot_.process_id, activity.user_data_ref, | |
| activity.user_data_id)); | |
| } | |
| } | |
| GlobalActivityAnalyzer::GlobalActivityAnalyzer( | |
| std::unique_ptr<PersistentMemoryAllocator> allocator) | |
| : allocator_(std::move(allocator)), | |
| analysis_stamp_(0LL), | |
| allocator_iterator_(allocator_.get()) { | |
| DCHECK(allocator_); | |
| } | |
| GlobalActivityAnalyzer::~GlobalActivityAnalyzer() = default; | |
| // static | |
| std::unique_ptr<GlobalActivityAnalyzer> | |
| GlobalActivityAnalyzer::CreateWithAllocator( | |
| std::unique_ptr<PersistentMemoryAllocator> allocator) { | |
| if (allocator->GetMemoryState() == | |
| PersistentMemoryAllocator::MEMORY_UNINITIALIZED) { | |
| LogAnalyzerCreationError(kPmaUninitialized); | |
| return nullptr; | |
| } | |
| if (allocator->GetMemoryState() == | |
| PersistentMemoryAllocator::MEMORY_DELETED) { | |
| LogAnalyzerCreationError(kPmaDeleted); | |
| return nullptr; | |
| } | |
| if (allocator->IsCorrupt()) { | |
| LogAnalyzerCreationError(kPmaCorrupt); | |
| return nullptr; | |
| } | |
| return WrapUnique(new GlobalActivityAnalyzer(std::move(allocator))); | |
| } | |
| #if !defined(OS_NACL) | |
| // static | |
| std::unique_ptr<GlobalActivityAnalyzer> GlobalActivityAnalyzer::CreateWithFile( | |
| const FilePath& file_path) { | |
| // Map the file read-write so it can guarantee consistency between | |
| // the analyzer and any trackers that my still be active. | |
| std::unique_ptr<MemoryMappedFile> mmfile(new MemoryMappedFile()); | |
| if (!mmfile->Initialize(file_path, MemoryMappedFile::READ_WRITE)) { | |
| LogAnalyzerCreationError(kInvalidMemoryMappedFile); | |
| return nullptr; | |
| } | |
| if (!FilePersistentMemoryAllocator::IsFileAcceptable(*mmfile, true)) { | |
| LogAnalyzerCreationError(kPmaBadFile); | |
| return nullptr; | |
| } | |
| return CreateWithAllocator(std::make_unique<FilePersistentMemoryAllocator>( | |
| std::move(mmfile), 0, 0, StringPiece(), /*readonly=*/true)); | |
| } | |
| #endif // !defined(OS_NACL) | |
| #if !defined(STARBOARD) | |
| // static | |
| std::unique_ptr<GlobalActivityAnalyzer> | |
| GlobalActivityAnalyzer::CreateWithSharedMemory( | |
| std::unique_ptr<SharedMemory> shm) { | |
| if (shm->mapped_size() == 0 || | |
| !SharedPersistentMemoryAllocator::IsSharedMemoryAcceptable(*shm)) { | |
| return nullptr; | |
| } | |
| return CreateWithAllocator(std::make_unique<SharedPersistentMemoryAllocator>( | |
| std::move(shm), 0, StringPiece(), /*readonly=*/true)); | |
| } | |
| // static | |
| std::unique_ptr<GlobalActivityAnalyzer> | |
| GlobalActivityAnalyzer::CreateWithSharedMemoryHandle( | |
| const SharedMemoryHandle& handle, | |
| size_t size) { | |
| std::unique_ptr<SharedMemory> shm( | |
| new SharedMemory(handle, /*readonly=*/true)); | |
| if (!shm->Map(size)) | |
| return nullptr; | |
| return CreateWithSharedMemory(std::move(shm)); | |
| } | |
| #endif // !defined(STARBOARD) | |
| int64_t GlobalActivityAnalyzer::GetFirstProcess() { | |
| PrepareAllAnalyzers(); | |
| return GetNextProcess(); | |
| } | |
| int64_t GlobalActivityAnalyzer::GetNextProcess() { | |
| if (process_ids_.empty()) | |
| return 0; | |
| int64_t pid = process_ids_.back(); | |
| process_ids_.pop_back(); | |
| return pid; | |
| } | |
| ThreadActivityAnalyzer* GlobalActivityAnalyzer::GetFirstAnalyzer(int64_t pid) { | |
| analyzers_iterator_ = analyzers_.begin(); | |
| analyzers_iterator_pid_ = pid; | |
| if (analyzers_iterator_ == analyzers_.end()) | |
| return nullptr; | |
| int64_t create_stamp; | |
| if (analyzers_iterator_->second->GetProcessId(&create_stamp) == pid && | |
| create_stamp <= analysis_stamp_) { | |
| return analyzers_iterator_->second.get(); | |
| } | |
| return GetNextAnalyzer(); | |
| } | |
| ThreadActivityAnalyzer* GlobalActivityAnalyzer::GetNextAnalyzer() { | |
| DCHECK(analyzers_iterator_ != analyzers_.end()); | |
| int64_t create_stamp; | |
| do { | |
| ++analyzers_iterator_; | |
| if (analyzers_iterator_ == analyzers_.end()) | |
| return nullptr; | |
| } while (analyzers_iterator_->second->GetProcessId(&create_stamp) != | |
| analyzers_iterator_pid_ || | |
| create_stamp > analysis_stamp_); | |
| return analyzers_iterator_->second.get(); | |
| } | |
| ThreadActivityAnalyzer* GlobalActivityAnalyzer::GetAnalyzerForThread( | |
| const ThreadKey& key) { | |
| auto found = analyzers_.find(key); | |
| if (found == analyzers_.end()) | |
| return nullptr; | |
| return found->second.get(); | |
| } | |
| ActivityUserData::Snapshot GlobalActivityAnalyzer::GetUserDataSnapshot( | |
| int64_t pid, | |
| uint32_t ref, | |
| uint32_t id) { | |
| ActivityUserData::Snapshot snapshot; | |
| void* memory = allocator_->GetAsArray<char>( | |
| ref, GlobalActivityTracker::kTypeIdUserDataRecord, | |
| PersistentMemoryAllocator::kSizeAny); | |
| if (memory) { | |
| size_t size = allocator_->GetAllocSize(ref); | |
| const ActivityUserData user_data(memory, size); | |
| user_data.CreateSnapshot(&snapshot); | |
| int64_t process_id; | |
| int64_t create_stamp; | |
| if (!ActivityUserData::GetOwningProcessId(memory, &process_id, | |
| &create_stamp) || | |
| process_id != pid || user_data.id() != id) { | |
| // This allocation has been overwritten since it was created. Return an | |
| // empty snapshot because whatever was captured is incorrect. | |
| snapshot.clear(); | |
| } | |
| } | |
| return snapshot; | |
| } | |
| const ActivityUserData::Snapshot& | |
| GlobalActivityAnalyzer::GetProcessDataSnapshot(int64_t pid) { | |
| auto iter = process_data_.find(pid); | |
| if (iter == process_data_.end()) | |
| return g_empty_user_data_snapshot.Get(); | |
| if (iter->second.create_stamp > analysis_stamp_) | |
| return g_empty_user_data_snapshot.Get(); | |
| DCHECK_EQ(pid, iter->second.process_id); | |
| return iter->second.data; | |
| } | |
| std::vector<std::string> GlobalActivityAnalyzer::GetLogMessages() { | |
| std::vector<std::string> messages; | |
| PersistentMemoryAllocator::Reference ref; | |
| PersistentMemoryAllocator::Iterator iter(allocator_.get()); | |
| while ((ref = iter.GetNextOfType( | |
| GlobalActivityTracker::kTypeIdGlobalLogMessage)) != 0) { | |
| const char* message = allocator_->GetAsArray<char>( | |
| ref, GlobalActivityTracker::kTypeIdGlobalLogMessage, | |
| PersistentMemoryAllocator::kSizeAny); | |
| if (message) | |
| messages.push_back(message); | |
| } | |
| return messages; | |
| } | |
| std::vector<GlobalActivityTracker::ModuleInfo> | |
| GlobalActivityAnalyzer::GetModules(int64_t pid) { | |
| std::vector<GlobalActivityTracker::ModuleInfo> modules; | |
| PersistentMemoryAllocator::Iterator iter(allocator_.get()); | |
| const GlobalActivityTracker::ModuleInfoRecord* record; | |
| while ( | |
| (record = | |
| iter.GetNextOfObject<GlobalActivityTracker::ModuleInfoRecord>()) != | |
| nullptr) { | |
| int64_t process_id; | |
| int64_t create_stamp; | |
| if (!OwningProcess::GetOwningProcessId(&record->owner, &process_id, | |
| &create_stamp) || | |
| pid != process_id || create_stamp > analysis_stamp_) { | |
| continue; | |
| } | |
| GlobalActivityTracker::ModuleInfo info; | |
| if (record->DecodeTo(&info, allocator_->GetAllocSize( | |
| allocator_->GetAsReference(record)))) { | |
| modules.push_back(std::move(info)); | |
| } | |
| } | |
| return modules; | |
| } | |
| GlobalActivityAnalyzer::ProgramLocation | |
| GlobalActivityAnalyzer::GetProgramLocationFromAddress(uint64_t address) { | |
| // TODO(bcwhite): Implement this. | |
| return { 0, 0 }; | |
| } | |
| bool GlobalActivityAnalyzer::IsDataComplete() const { | |
| DCHECK(allocator_); | |
| return !allocator_->IsFull(); | |
| } | |
| GlobalActivityAnalyzer::UserDataSnapshot::UserDataSnapshot() = default; | |
| GlobalActivityAnalyzer::UserDataSnapshot::UserDataSnapshot( | |
| const UserDataSnapshot& rhs) = default; | |
| GlobalActivityAnalyzer::UserDataSnapshot::UserDataSnapshot( | |
| UserDataSnapshot&& rhs) = default; | |
| GlobalActivityAnalyzer::UserDataSnapshot::~UserDataSnapshot() = default; | |
| void GlobalActivityAnalyzer::PrepareAllAnalyzers() { | |
| // Record the time when analysis started. | |
| analysis_stamp_ = base::Time::Now().ToInternalValue(); | |
| // Fetch all the records. This will retrieve only ones created since the | |
| // last run since the PMA iterator will continue from where it left off. | |
| uint32_t type; | |
| PersistentMemoryAllocator::Reference ref; | |
| while ((ref = allocator_iterator_.GetNext(&type)) != 0) { | |
| switch (type) { | |
| case GlobalActivityTracker::kTypeIdActivityTracker: | |
| case GlobalActivityTracker::kTypeIdActivityTrackerFree: | |
| case GlobalActivityTracker::kTypeIdProcessDataRecord: | |
| case GlobalActivityTracker::kTypeIdProcessDataRecordFree: | |
| case PersistentMemoryAllocator::kTypeIdTransitioning: | |
| // Active, free, or transitioning: add it to the list of references | |
| // for later analysis. | |
| memory_references_.insert(ref); | |
| break; | |
| } | |
| } | |
| // Clear out any old information. | |
| analyzers_.clear(); | |
| process_data_.clear(); | |
| process_ids_.clear(); | |
| std::set<int64_t> seen_pids; | |
| // Go through all the known references and create objects for them with | |
| // snapshots of the current state. | |
| for (PersistentMemoryAllocator::Reference memory_ref : memory_references_) { | |
| // Get the actual data segment for the tracker. Any type will do since it | |
| // is checked below. | |
| void* const base = allocator_->GetAsArray<char>( | |
| memory_ref, PersistentMemoryAllocator::kTypeIdAny, | |
| PersistentMemoryAllocator::kSizeAny); | |
| const size_t size = allocator_->GetAllocSize(memory_ref); | |
| if (!base) | |
| continue; | |
| switch (allocator_->GetType(memory_ref)) { | |
| case GlobalActivityTracker::kTypeIdActivityTracker: { | |
| // Create the analyzer on the data. This will capture a snapshot of the | |
| // tracker state. This can fail if the tracker is somehow corrupted or | |
| // is in the process of shutting down. | |
| std::unique_ptr<ThreadActivityAnalyzer> analyzer( | |
| new ThreadActivityAnalyzer(base, size)); | |
| if (!analyzer->IsValid()) | |
| continue; | |
| analyzer->AddGlobalInformation(this); | |
| // Track PIDs. | |
| int64_t pid = analyzer->GetProcessId(); | |
| if (seen_pids.find(pid) == seen_pids.end()) { | |
| process_ids_.push_back(pid); | |
| seen_pids.insert(pid); | |
| } | |
| // Add this analyzer to the map of known ones, indexed by a unique | |
| // thread | |
| // identifier. | |
| DCHECK(!base::ContainsKey(analyzers_, analyzer->GetThreadKey())); | |
| analyzer->allocator_reference_ = ref; | |
| analyzers_[analyzer->GetThreadKey()] = std::move(analyzer); | |
| } break; | |
| case GlobalActivityTracker::kTypeIdProcessDataRecord: { | |
| // Get the PID associated with this data record. | |
| int64_t process_id; | |
| int64_t create_stamp; | |
| ActivityUserData::GetOwningProcessId(base, &process_id, &create_stamp); | |
| DCHECK(!base::ContainsKey(process_data_, process_id)); | |
| // Create a snapshot of the data. This can fail if the data is somehow | |
| // corrupted or the process shutdown and the memory being released. | |
| UserDataSnapshot& snapshot = process_data_[process_id]; | |
| snapshot.process_id = process_id; | |
| snapshot.create_stamp = create_stamp; | |
| const ActivityUserData process_data(base, size); | |
| if (!process_data.CreateSnapshot(&snapshot.data)) | |
| break; | |
| // Check that nothing changed. If it did, forget what was recorded. | |
| ActivityUserData::GetOwningProcessId(base, &process_id, &create_stamp); | |
| if (process_id != snapshot.process_id || | |
| create_stamp != snapshot.create_stamp) { | |
| process_data_.erase(process_id); | |
| break; | |
| } | |
| // Track PIDs. | |
| if (seen_pids.find(process_id) == seen_pids.end()) { | |
| process_ids_.push_back(process_id); | |
| seen_pids.insert(process_id); | |
| } | |
| } break; | |
| } | |
| } | |
| // Reverse the list of PIDs so that they get popped in the order found. | |
| std::reverse(process_ids_.begin(), process_ids_.end()); | |
| } | |
| } // namespace debug | |
| } // namespace base |