Remove browser_watcher (2/3): Delete components/browser_watcher.
Also deletes the related classes in base/debug/activity_analyzer.* and base/debug/activity_tracker.*. Many files include base/debug/activity_tracker.h and do work iff a GlobalActivityTracker object exists. To keep this patch self-contained it replaces the classes in activity_tracker.h with no-op stubs instead of deleting it, so that all files that include it will still compile. A followup will delete activity_tracker.h and all uses of it. This should not cause any behaviour change because GlobalActivityTracker is only created when the ExtendedCrashReporting feature is enabled, so all uses of activity_tracker.h are already no-ops in production. Bug: 1415328 Change-Id: I940fb0e013a3ae7a939136099f34791dec914db9 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4265975 Reviewed-by: Colin Blundell <blundell@chromium.org> Commit-Queue: Joe Mason <joenotcharles@google.com> Reviewed-by: Daniel Cheng <dcheng@chromium.org> Cr-Commit-Position: refs/heads/main@{#1107725}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
03594b2987
commit
804d3d151b
PRESUBMIT.py
base
BUILD.gn
debug
OWNERSactivity_analyzer.ccactivity_analyzer.hactivity_analyzer_fuzzer.ccactivity_analyzer_fuzzer.dictactivity_analyzer_unittest.ccactivity_tracker.ccactivity_tracker.hactivity_tracker_unittest.cc
synchronization
build
chrome
components
BUILD.gn
browser_watcher
BUILD.gnDEPSDIR_METADATAOWNERSactivity_data_names.ccactivity_data_names.hactivity_report.protoactivity_report_extractor.ccactivity_report_extractor.hactivity_report_extractor_unittest.ccactivity_report_user_stream_data_source.ccactivity_report_user_stream_data_source.hactivity_tracker_annotation.ccactivity_tracker_annotation.hactivity_tracker_annotation_unittest.ccdump_stability_report_main_win.ccextended_crash_reporting.ccextended_crash_reporting.hextended_crash_reporting_metrics.ccextended_crash_reporting_metrics.hextended_crash_reporting_win_unittest.ccfeatures.ccfeatures.hfetch_system_session_events_main_win.ccminidump_user_streams.h
keep_alive_registry
metrics
nacl
broker
@ -2949,7 +2949,6 @@ def CheckSpamLogging(input_api, output_api):
|
||||
r"^chrome/chrome_elf/dll_hash/dll_hash_main\.cc$",
|
||||
r"^chrome/installer/setup/.*",
|
||||
r"^chromecast/",
|
||||
r"^components/browser_watcher/dump_stability_report_main_win\.cc$",
|
||||
r"^components/media_control/renderer/media_playback_options\.cc$",
|
||||
r"^components/policy/core/common/policy_logger\.cc$",
|
||||
r"^components/viz/service/display/"
|
||||
|
@ -308,9 +308,6 @@ component("base") {
|
||||
"cxx20_is_constant_evaluated.h",
|
||||
"cxx20_to_address.h",
|
||||
"dcheck_is_on.h",
|
||||
"debug/activity_analyzer.cc",
|
||||
"debug/activity_analyzer.h",
|
||||
"debug/activity_tracker.cc",
|
||||
"debug/activity_tracker.h",
|
||||
"debug/alias.cc",
|
||||
"debug/alias.h",
|
||||
@ -3033,8 +3030,6 @@ test("base_unittests") {
|
||||
"cpu_unittest.cc",
|
||||
"cxx17_backports_unittest.cc",
|
||||
"cxx20_is_constant_evaluated_unittest.cc",
|
||||
"debug/activity_analyzer_unittest.cc",
|
||||
"debug/activity_tracker_unittest.cc",
|
||||
"debug/alias_unittest.cc",
|
||||
"debug/asan_service_unittest.cc",
|
||||
"debug/crash_logging_unittest.cc",
|
||||
@ -4650,13 +4645,6 @@ fuzzer_test("base_json_string_escape_fuzzer") {
|
||||
deps = [ "//base" ]
|
||||
}
|
||||
|
||||
fuzzer_test("base_activity_analyzer_fuzzer") {
|
||||
sources = [ "debug/activity_analyzer_fuzzer.cc" ]
|
||||
deps = [ "//base" ]
|
||||
dict = "debug/activity_analyzer_fuzzer.dict"
|
||||
seed_corpus = "test/data/activity_analyzer_fuzzer"
|
||||
}
|
||||
|
||||
if (is_mac) {
|
||||
protoc_convert("base_mach_port_rendezvous_convert_corpus") {
|
||||
sources = [
|
||||
|
@ -1,5 +1,2 @@
|
||||
# For activity tracking:
|
||||
per-file activity_*=bcwhite@chromium.org
|
||||
|
||||
# For ASan integration:
|
||||
per-file asan_service*=file://base/memory/MIRACLE_PTR_OWNERS
|
||||
|
@ -1,407 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// 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 <utility>
|
||||
|
||||
#include "base/check_op.h"
|
||||
#include "base/containers/contains.h"
|
||||
#include "base/files/file.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/memory_mapped_file.h"
|
||||
#include "base/metrics/histogram_functions.h"
|
||||
#include "base/metrics/histogram_macros.h"
|
||||
#include "base/no_destructor.h"
|
||||
#include "base/ranges/algorithm.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
namespace {
|
||||
|
||||
const ActivityUserData::Snapshot& GetEmptyUserDataSnapshot() {
|
||||
// An empty snapshot that can be returned when there otherwise is none.
|
||||
static const NoDestructor<ActivityUserData::Snapshot> empty_snapshot;
|
||||
return *empty_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) {
|
||||
UmaHistogramEnumeration("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 std::make_unique<GlobalActivityAnalyzer>(std::move(allocator));
|
||||
}
|
||||
|
||||
#if !BUILDFLAG(IS_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 // !BUILDFLAG(IS_NACL)
|
||||
|
||||
// static
|
||||
std::unique_ptr<GlobalActivityAnalyzer>
|
||||
GlobalActivityAnalyzer::CreateWithSharedMemory(
|
||||
base::ReadOnlySharedMemoryMapping mapping) {
|
||||
if (!mapping.IsValid() ||
|
||||
!ReadOnlySharedPersistentMemoryAllocator::IsSharedMemoryAcceptable(
|
||||
mapping)) {
|
||||
return nullptr;
|
||||
}
|
||||
return CreateWithAllocator(
|
||||
std::make_unique<ReadOnlySharedPersistentMemoryAllocator>(
|
||||
std::move(mapping), 0, StringPiece()));
|
||||
}
|
||||
|
||||
ProcessId GlobalActivityAnalyzer::GetFirstProcess() {
|
||||
PrepareAllAnalyzers();
|
||||
return GetNextProcess();
|
||||
}
|
||||
|
||||
ProcessId GlobalActivityAnalyzer::GetNextProcess() {
|
||||
if (process_ids_.empty())
|
||||
return 0;
|
||||
ProcessId pid = process_ids_.back();
|
||||
process_ids_.pop_back();
|
||||
return pid;
|
||||
}
|
||||
|
||||
ThreadActivityAnalyzer* GlobalActivityAnalyzer::GetFirstAnalyzer(
|
||||
ProcessId 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(
|
||||
ProcessId 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);
|
||||
ProcessId 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(ProcessId pid) {
|
||||
auto iter = process_data_.find(pid);
|
||||
if (iter == process_data_.end())
|
||||
return GetEmptyUserDataSnapshot();
|
||||
if (iter->second.create_stamp > analysis_stamp_)
|
||||
return GetEmptyUserDataSnapshot();
|
||||
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(ProcessId pid) {
|
||||
std::vector<GlobalActivityTracker::ModuleInfo> modules;
|
||||
|
||||
PersistentMemoryAllocator::Iterator iter(allocator_.get());
|
||||
const GlobalActivityTracker::ModuleInfoRecord* record;
|
||||
while (
|
||||
(record =
|
||||
iter.GetNextOfObject<GlobalActivityTracker::ModuleInfoRecord>()) !=
|
||||
nullptr) {
|
||||
ProcessId 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) {
|
||||
// This should be implemented but it's never been a priority.
|
||||
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<ProcessId> 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.
|
||||
ProcessId 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::Contains(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.
|
||||
ProcessId process_id;
|
||||
int64_t create_stamp;
|
||||
ActivityUserData::GetOwningProcessId(base, &process_id, &create_stamp);
|
||||
DCHECK(!base::Contains(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.
|
||||
ranges::reverse(process_ids_);
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
@ -1,260 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_DEBUG_ACTIVITY_ANALYZER_H_
|
||||
#define BASE_DEBUG_ACTIVITY_ANALYZER_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/base_export.h"
|
||||
#include "base/debug/activity_tracker.h"
|
||||
#include "base/memory/shared_memory_mapping.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
class GlobalActivityAnalyzer;
|
||||
|
||||
// This class provides analysis of data captured from a ThreadActivityTracker.
|
||||
// When created, it takes a snapshot of the data held by the tracker and
|
||||
// makes that information available to other code.
|
||||
class BASE_EXPORT ThreadActivityAnalyzer {
|
||||
public:
|
||||
struct BASE_EXPORT Snapshot : ThreadActivityTracker::Snapshot {
|
||||
Snapshot();
|
||||
~Snapshot();
|
||||
|
||||
// The user-data snapshot for an activity, matching the |activity_stack|
|
||||
// of ThreadActivityTracker::Snapshot, if any.
|
||||
std::vector<ActivityUserData::Snapshot> user_data_stack;
|
||||
};
|
||||
|
||||
// This class provides keys that uniquely identify a thread, even across
|
||||
// multiple processes.
|
||||
class ThreadKey {
|
||||
public:
|
||||
ThreadKey(ProcessId pid, int64_t tid) : pid_(pid), tid_(tid) {}
|
||||
|
||||
bool operator<(const ThreadKey& rhs) const {
|
||||
if (pid_ != rhs.pid_)
|
||||
return pid_ < rhs.pid_;
|
||||
return tid_ < rhs.tid_;
|
||||
}
|
||||
|
||||
bool operator==(const ThreadKey& rhs) const {
|
||||
return (pid_ == rhs.pid_ && tid_ == rhs.tid_);
|
||||
}
|
||||
|
||||
private:
|
||||
ProcessId pid_;
|
||||
int64_t tid_;
|
||||
};
|
||||
|
||||
// Creates an analyzer for an existing activity |tracker|. A snapshot is taken
|
||||
// immediately and the tracker is not referenced again.
|
||||
explicit ThreadActivityAnalyzer(const ThreadActivityTracker& tracker);
|
||||
|
||||
// Creates an analyzer for a block of memory currently or previously in-use
|
||||
// by an activity-tracker. A snapshot is taken immediately and the memory
|
||||
// is not referenced again.
|
||||
ThreadActivityAnalyzer(void* base, size_t size);
|
||||
|
||||
// Creates an analyzer for a block of memory held within a persistent-memory
|
||||
// |allocator| at the given |reference|. A snapshot is taken immediately and
|
||||
// the memory is not referenced again.
|
||||
ThreadActivityAnalyzer(PersistentMemoryAllocator* allocator,
|
||||
PersistentMemoryAllocator::Reference reference);
|
||||
|
||||
ThreadActivityAnalyzer(const ThreadActivityAnalyzer&) = delete;
|
||||
ThreadActivityAnalyzer& operator=(const ThreadActivityAnalyzer&) = delete;
|
||||
|
||||
~ThreadActivityAnalyzer();
|
||||
|
||||
// Adds information from the global analyzer.
|
||||
void AddGlobalInformation(GlobalActivityAnalyzer* global);
|
||||
|
||||
// Returns true iff the contained data is valid. Results from all other
|
||||
// methods are undefined if this returns false.
|
||||
bool IsValid() { return activity_snapshot_valid_; }
|
||||
|
||||
// Gets the process id and its creation stamp.
|
||||
ProcessId GetProcessId(int64_t* out_stamp = nullptr) {
|
||||
if (out_stamp)
|
||||
*out_stamp = activity_snapshot_.create_stamp;
|
||||
return activity_snapshot_.process_id;
|
||||
}
|
||||
|
||||
// Gets the name of the thread.
|
||||
const std::string& GetThreadName() {
|
||||
return activity_snapshot_.thread_name;
|
||||
}
|
||||
|
||||
// Gets the TheadKey for this thread.
|
||||
ThreadKey GetThreadKey() {
|
||||
return ThreadKey(activity_snapshot_.process_id,
|
||||
activity_snapshot_.thread_id);
|
||||
}
|
||||
|
||||
const Snapshot& activity_snapshot() { return activity_snapshot_; }
|
||||
|
||||
private:
|
||||
friend class GlobalActivityAnalyzer;
|
||||
|
||||
// The snapshot of the activity tracker taken at the moment of construction.
|
||||
Snapshot activity_snapshot_;
|
||||
|
||||
// Flag indicating if the snapshot data is valid.
|
||||
bool activity_snapshot_valid_;
|
||||
|
||||
// A reference into a persistent memory allocator, used by the global
|
||||
// analyzer to know where this tracker came from.
|
||||
PersistentMemoryAllocator::Reference allocator_reference_ = 0;
|
||||
};
|
||||
|
||||
|
||||
// This class manages analyzers for all known processes and threads as stored
|
||||
// in a persistent memory allocator. It supports retrieval of them through
|
||||
// iteration and directly using a ThreadKey, which allows for cross-references
|
||||
// to be resolved.
|
||||
// Note that though atomic snapshots are used and everything has its snapshot
|
||||
// taken at the same time, the multi-snapshot itself is not atomic and thus may
|
||||
// show small inconsistencies between threads if attempted on a live system.
|
||||
class BASE_EXPORT GlobalActivityAnalyzer {
|
||||
public:
|
||||
struct ProgramLocation {
|
||||
int module;
|
||||
uintptr_t offset;
|
||||
};
|
||||
|
||||
using ThreadKey = ThreadActivityAnalyzer::ThreadKey;
|
||||
|
||||
// Creates a global analyzer from a persistent memory allocator.
|
||||
explicit GlobalActivityAnalyzer(
|
||||
std::unique_ptr<PersistentMemoryAllocator> allocator);
|
||||
|
||||
GlobalActivityAnalyzer(const GlobalActivityAnalyzer&) = delete;
|
||||
GlobalActivityAnalyzer& operator=(const GlobalActivityAnalyzer&) = delete;
|
||||
|
||||
~GlobalActivityAnalyzer();
|
||||
|
||||
// Creates a global analyzer using a given persistent-memory |allocator|.
|
||||
static std::unique_ptr<GlobalActivityAnalyzer> CreateWithAllocator(
|
||||
std::unique_ptr<PersistentMemoryAllocator> allocator);
|
||||
|
||||
#if !BUILDFLAG(IS_NACL)
|
||||
// Creates a global analyzer using the contents of a file given in
|
||||
// |file_path|.
|
||||
static std::unique_ptr<GlobalActivityAnalyzer> CreateWithFile(
|
||||
const FilePath& file_path);
|
||||
#endif // !BUILDFLAG(IS_NACL)
|
||||
|
||||
// Like above but accesses an allocator in a mapped shared-memory segment.
|
||||
static std::unique_ptr<GlobalActivityAnalyzer> CreateWithSharedMemory(
|
||||
base::ReadOnlySharedMemoryMapping mapping);
|
||||
|
||||
// Iterates over all known valid processes and returns their PIDs or zero
|
||||
// if there are no more. Calls to GetFirstProcess() will perform a global
|
||||
// snapshot in order to provide a relatively consistent state across the
|
||||
// future calls to GetNextProcess() and GetFirst/NextAnalyzer(). PIDs are
|
||||
// returned in the order they're found meaning that a first-launched
|
||||
// controlling process will be found first. Note, however, that space
|
||||
// freed by an exiting process may be re-used by a later process.
|
||||
ProcessId GetFirstProcess();
|
||||
ProcessId GetNextProcess();
|
||||
|
||||
// Iterates over all known valid analyzers for the a given process or returns
|
||||
// null if there are no more.
|
||||
//
|
||||
// GetFirstProcess() must be called first in order to capture a global
|
||||
// snapshot! Ownership stays with the global analyzer object and all existing
|
||||
// analyzer pointers are invalidated when GetFirstProcess() is called.
|
||||
ThreadActivityAnalyzer* GetFirstAnalyzer(ProcessId pid);
|
||||
ThreadActivityAnalyzer* GetNextAnalyzer();
|
||||
|
||||
// Gets the analyzer for a specific thread or null if there is none.
|
||||
// Ownership stays with the global analyzer object.
|
||||
ThreadActivityAnalyzer* GetAnalyzerForThread(const ThreadKey& key);
|
||||
|
||||
// Extract user data based on a reference and its identifier.
|
||||
ActivityUserData::Snapshot GetUserDataSnapshot(ProcessId pid,
|
||||
uint32_t ref,
|
||||
uint32_t id);
|
||||
|
||||
// Extract the data for a specific process. An empty snapshot will be
|
||||
// returned if the process is not known.
|
||||
const ActivityUserData::Snapshot& GetProcessDataSnapshot(ProcessId pid);
|
||||
|
||||
// Gets all log messages stored within.
|
||||
std::vector<std::string> GetLogMessages();
|
||||
|
||||
// Gets modules corresponding to a pid. This pid must come from a call to
|
||||
// GetFirst/NextProcess. Only modules that were first registered prior to
|
||||
// GetFirstProcess's snapshot are returned.
|
||||
std::vector<GlobalActivityTracker::ModuleInfo> GetModules(ProcessId pid);
|
||||
|
||||
// Gets the corresponding "program location" for a given "program counter".
|
||||
// This will return {0,0} if no mapping could be found.
|
||||
ProgramLocation GetProgramLocationFromAddress(uint64_t address);
|
||||
|
||||
// Returns whether the data is complete. Data can be incomplete if the
|
||||
// recording size quota is hit.
|
||||
bool IsDataComplete() const;
|
||||
|
||||
private:
|
||||
using AnalyzerMap =
|
||||
std::map<ThreadKey, std::unique_ptr<ThreadActivityAnalyzer>>;
|
||||
|
||||
struct UserDataSnapshot {
|
||||
// Complex class needs out-of-line ctor/dtor.
|
||||
UserDataSnapshot();
|
||||
UserDataSnapshot(const UserDataSnapshot& rhs);
|
||||
UserDataSnapshot(UserDataSnapshot&& rhs);
|
||||
~UserDataSnapshot();
|
||||
|
||||
ProcessId process_id;
|
||||
int64_t create_stamp;
|
||||
ActivityUserData::Snapshot data;
|
||||
};
|
||||
|
||||
// Finds, creates, and indexes analyzers for all known processes and threads.
|
||||
void PrepareAllAnalyzers();
|
||||
|
||||
// The persistent memory allocator holding all tracking data.
|
||||
std::unique_ptr<PersistentMemoryAllocator> allocator_;
|
||||
|
||||
// The time stamp when analysis began. This is used to prevent looking into
|
||||
// process IDs that get reused when analyzing a live system.
|
||||
int64_t analysis_stamp_;
|
||||
|
||||
// The iterator for finding tracking information in the allocator.
|
||||
PersistentMemoryAllocator::Iterator allocator_iterator_;
|
||||
|
||||
// A set of all interesting memory references found within the allocator.
|
||||
std::set<PersistentMemoryAllocator::Reference> memory_references_;
|
||||
|
||||
// A set of all process-data memory references found within the allocator.
|
||||
std::map<ProcessId, UserDataSnapshot> process_data_;
|
||||
|
||||
// A set of all process IDs collected during PrepareAllAnalyzers. These are
|
||||
// popped and returned one-by-one with calls to GetFirst/NextProcess().
|
||||
std::vector<ProcessId> process_ids_;
|
||||
|
||||
// A map, keyed by ThreadKey, of all valid activity analyzers.
|
||||
AnalyzerMap analyzers_;
|
||||
|
||||
// The iterator within the analyzers_ map for returning analyzers through
|
||||
// first/next iteration.
|
||||
AnalyzerMap::iterator analyzers_iterator_;
|
||||
ProcessId analyzers_iterator_pid_;
|
||||
};
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_DEBUG_ACTIVITY_ANALYZER_H_
|
@ -1,31 +0,0 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// 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 "base/logging.h"
|
||||
#include "base/metrics/persistent_memory_allocator.h"
|
||||
|
||||
struct Environment {
|
||||
Environment() { logging::SetMinLogLevel(logging::LOG_FATAL); }
|
||||
};
|
||||
|
||||
// Entry point for LibFuzzer.
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
static Environment env;
|
||||
|
||||
if (size < 64u) { // sizeof(base::PersistentMemoryAllocator::SharedMetadata)
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<base::PersistentMemoryAllocator> allocator =
|
||||
std::make_unique<base::PersistentMemoryAllocator>(
|
||||
const_cast<uint8_t*>(data), size, /*page_size=*/0, /*id=*/0,
|
||||
/*name=*/"",
|
||||
/*read_only=*/true);
|
||||
|
||||
base::debug::GlobalActivityAnalyzer gaa(std::move(allocator));
|
||||
std::ignore = gaa.GetFirstProcess();
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
"\x00\x00\x00\x00"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
|
||||
kTypeIdActivityTracker = "\xB3\x81\x73\x5D"
|
||||
kTypeIdUserDataRecord = "\xDA\xDD\x5E\x61"
|
||||
kTypeIdGlobalLogMessage = "\xFA\x34\xF4\x4C"
|
||||
kTypeIdProcessDataRecord = "\xDA\xDE\x5E\x61"
|
||||
kTypeIdActivityTrackerFree = "\x4C\x7E\x8C\xA2"
|
||||
kTypeIdUserDataRecordFree = "\x25\x22\xA1\x9E"
|
||||
kTypeIdProcessDataRecordFree = "\x25\x21\xA1\x9E"
|
||||
|
||||
kGlobalCookie = "\xDC\x05\x83\x40"
|
||||
kBlockCookieAllocated = "\x69\x92\x79\xC8"
|
@ -1,562 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// 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 <atomic>
|
||||
#include <memory>
|
||||
|
||||
#include "base/auto_reset.h"
|
||||
#include "base/containers/contains.h"
|
||||
#include "base/debug/activity_tracker.h"
|
||||
#include "base/files/file.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/files/memory_mapped_file.h"
|
||||
#include "base/files/scoped_temp_dir.h"
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/memory/ptr_util.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/memory/read_only_shared_memory_region.h"
|
||||
#include "base/pending_task.h"
|
||||
#include "base/process/process.h"
|
||||
#include "base/strings/string_piece.h"
|
||||
#include "base/synchronization/condition_variable.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "base/test/spin_wait.h"
|
||||
#include "base/threading/platform_thread.h"
|
||||
#include "base/threading/simple_thread.h"
|
||||
#include "base/time/time.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
namespace {
|
||||
|
||||
class TestActivityTracker {
|
||||
public:
|
||||
TestActivityTracker(std::unique_ptr<char[]> memory, size_t mem_size)
|
||||
: mem_segment_(std::move(memory)),
|
||||
tracker_(memset(mem_segment_.get(), 0, mem_size), mem_size) {}
|
||||
|
||||
~TestActivityTracker() = default;
|
||||
|
||||
ThreadActivityTracker& tracker() { return tracker_; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<char[]> mem_segment_; // Must outlive `tracker_`
|
||||
ThreadActivityTracker tracker_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
class ActivityAnalyzerTest : public testing::Test {
|
||||
public:
|
||||
const int kMemorySize = 1 << 20; // 1MiB
|
||||
const int kStackSize = 1 << 10; // 1KiB
|
||||
|
||||
ActivityAnalyzerTest() = default;
|
||||
|
||||
~ActivityAnalyzerTest() override {
|
||||
GlobalActivityTracker* global_tracker = GlobalActivityTracker::Get();
|
||||
if (global_tracker) {
|
||||
global_tracker->ReleaseTrackerForCurrentThreadForTesting();
|
||||
delete global_tracker;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<TestActivityTracker> CreateActivityTracker() {
|
||||
std::unique_ptr<char[]> memory(new char[kStackSize]);
|
||||
return std::make_unique<TestActivityTracker>(std::move(memory), kStackSize);
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
void AsOtherProcess(ProcessId pid, Function function) {
|
||||
std::unique_ptr<GlobalActivityTracker> old_global =
|
||||
GlobalActivityTracker::ReleaseForTesting();
|
||||
ASSERT_TRUE(old_global);
|
||||
|
||||
PersistentMemoryAllocator* old_allocator = old_global->allocator();
|
||||
std::unique_ptr<PersistentMemoryAllocator> new_allocator(
|
||||
std::make_unique<PersistentMemoryAllocator>(
|
||||
const_cast<void*>(old_allocator->data()), old_allocator->size(), 0,
|
||||
0, "", false));
|
||||
GlobalActivityTracker::CreateWithAllocator(std::move(new_allocator), 3,
|
||||
pid);
|
||||
|
||||
function();
|
||||
|
||||
GlobalActivityTracker::ReleaseForTesting();
|
||||
GlobalActivityTracker::SetForTesting(std::move(old_global));
|
||||
}
|
||||
|
||||
static void DoNothing() {}
|
||||
};
|
||||
|
||||
TEST_F(ActivityAnalyzerTest, ThreadAnalyzerConstruction) {
|
||||
std::unique_ptr<TestActivityTracker> tracker = CreateActivityTracker();
|
||||
{
|
||||
ThreadActivityAnalyzer analyzer(tracker->tracker());
|
||||
EXPECT_TRUE(analyzer.IsValid());
|
||||
EXPECT_EQ(PlatformThread::GetName(), analyzer.GetThreadName());
|
||||
}
|
||||
|
||||
// More tests once Analyzer does more.
|
||||
}
|
||||
|
||||
|
||||
// GlobalActivityAnalyzer tests below.
|
||||
|
||||
namespace {
|
||||
|
||||
class SimpleActivityThread : public SimpleThread {
|
||||
public:
|
||||
SimpleActivityThread(const std::string& name,
|
||||
const void* source,
|
||||
Activity::Type activity,
|
||||
const ActivityData& data)
|
||||
: SimpleThread(name, Options()),
|
||||
source_(source),
|
||||
activity_(activity),
|
||||
data_(data),
|
||||
ready_(false),
|
||||
exit_(false),
|
||||
exit_condition_(&lock_) {}
|
||||
|
||||
SimpleActivityThread(const SimpleActivityThread&) = delete;
|
||||
SimpleActivityThread& operator=(const SimpleActivityThread&) = delete;
|
||||
|
||||
~SimpleActivityThread() override = default;
|
||||
|
||||
void Run() override {
|
||||
ThreadActivityTracker::ActivityId id =
|
||||
GlobalActivityTracker::Get()
|
||||
->GetOrCreateTrackerForCurrentThread()
|
||||
->PushActivity(source_, activity_, data_);
|
||||
|
||||
{
|
||||
AutoLock auto_lock(lock_);
|
||||
ready_.store(true, std::memory_order_release);
|
||||
while (!exit_.load(std::memory_order_relaxed))
|
||||
exit_condition_.Wait();
|
||||
}
|
||||
|
||||
GlobalActivityTracker::Get()->GetTrackerForCurrentThread()->PopActivity(id);
|
||||
}
|
||||
|
||||
void Exit() {
|
||||
AutoLock auto_lock(lock_);
|
||||
exit_.store(true, std::memory_order_relaxed);
|
||||
exit_condition_.Signal();
|
||||
}
|
||||
|
||||
void WaitReady() {
|
||||
SPIN_FOR_1_SECOND_OR_UNTIL_TRUE(ready_.load(std::memory_order_acquire));
|
||||
}
|
||||
|
||||
private:
|
||||
raw_ptr<const void> source_;
|
||||
Activity::Type activity_;
|
||||
ActivityData data_;
|
||||
|
||||
std::atomic<bool> ready_;
|
||||
std::atomic<bool> exit_;
|
||||
Lock lock_;
|
||||
ConditionVariable exit_condition_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// TODO(1061320): Flaky under tsan.
|
||||
#if defined(THREAD_SANITIZER)
|
||||
#define MAYBE_GlobalAnalyzerConstruction DISABLED_GlobalAnalyzerConstruction
|
||||
#else
|
||||
#define MAYBE_GlobalAnalyzerConstruction GlobalAnalyzerConstruction
|
||||
#endif
|
||||
TEST_F(ActivityAnalyzerTest, MAYBE_GlobalAnalyzerConstruction) {
|
||||
GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0);
|
||||
GlobalActivityTracker::Get()->process_data().SetString("foo", "bar");
|
||||
|
||||
PersistentMemoryAllocator* allocator =
|
||||
GlobalActivityTracker::Get()->allocator();
|
||||
GlobalActivityAnalyzer analyzer(std::make_unique<PersistentMemoryAllocator>(
|
||||
const_cast<void*>(allocator->data()), allocator->size(), 0, 0, "", true));
|
||||
|
||||
// The only thread at this point is the test thread of this process.
|
||||
const ProcessId pid = analyzer.GetFirstProcess();
|
||||
ASSERT_NE(ProcessId{0}, pid);
|
||||
ThreadActivityAnalyzer* ta1 = analyzer.GetFirstAnalyzer(pid);
|
||||
ASSERT_TRUE(ta1);
|
||||
EXPECT_FALSE(analyzer.GetNextAnalyzer());
|
||||
ThreadActivityAnalyzer::ThreadKey tk1 = ta1->GetThreadKey();
|
||||
EXPECT_EQ(ta1, analyzer.GetAnalyzerForThread(tk1));
|
||||
EXPECT_EQ(ProcessId{0}, analyzer.GetNextProcess());
|
||||
|
||||
// Create a second thread that will do something.
|
||||
SimpleActivityThread t2("t2", nullptr, Activity::ACT_TASK,
|
||||
ActivityData::ForTask(11));
|
||||
t2.Start();
|
||||
t2.WaitReady();
|
||||
|
||||
// Now there should be two. Calling GetFirstProcess invalidates any
|
||||
// previously returned analyzer pointers.
|
||||
ASSERT_EQ(pid, analyzer.GetFirstProcess());
|
||||
EXPECT_TRUE(analyzer.GetFirstAnalyzer(pid));
|
||||
EXPECT_TRUE(analyzer.GetNextAnalyzer());
|
||||
EXPECT_FALSE(analyzer.GetNextAnalyzer());
|
||||
EXPECT_EQ(ProcessId{0}, analyzer.GetNextProcess());
|
||||
|
||||
// Let thread exit.
|
||||
t2.Exit();
|
||||
t2.Join();
|
||||
|
||||
// Now there should be only one again.
|
||||
ASSERT_EQ(pid, analyzer.GetFirstProcess());
|
||||
ThreadActivityAnalyzer* ta2 = analyzer.GetFirstAnalyzer(pid);
|
||||
ASSERT_TRUE(ta2);
|
||||
EXPECT_FALSE(analyzer.GetNextAnalyzer());
|
||||
ThreadActivityAnalyzer::ThreadKey tk2 = ta2->GetThreadKey();
|
||||
EXPECT_EQ(ta2, analyzer.GetAnalyzerForThread(tk2));
|
||||
EXPECT_EQ(tk1, tk2);
|
||||
EXPECT_EQ(ProcessId{0}, analyzer.GetNextProcess());
|
||||
|
||||
// Verify that there is process data.
|
||||
const ActivityUserData::Snapshot& data_snapshot =
|
||||
analyzer.GetProcessDataSnapshot(pid);
|
||||
ASSERT_LE(1U, data_snapshot.size());
|
||||
EXPECT_EQ("bar", data_snapshot.at("foo").GetString());
|
||||
}
|
||||
|
||||
TEST_F(ActivityAnalyzerTest, GlobalAnalyzerFromSharedMemory) {
|
||||
base::MappedReadOnlyRegion shm =
|
||||
base::ReadOnlySharedMemoryRegion::Create(kMemorySize);
|
||||
ASSERT_TRUE(shm.IsValid());
|
||||
base::WritableSharedMemoryMapping rw_mapping = std::move(shm.mapping);
|
||||
base::ReadOnlySharedMemoryMapping ro_mapping = shm.region.Map();
|
||||
ASSERT_TRUE(ro_mapping.IsValid());
|
||||
|
||||
GlobalActivityTracker::CreateWithSharedMemory(std::move(rw_mapping), 0, "",
|
||||
3);
|
||||
GlobalActivityTracker::Get()->process_data().SetString("foo", "bar");
|
||||
|
||||
std::unique_ptr<GlobalActivityAnalyzer> analyzer =
|
||||
GlobalActivityAnalyzer::CreateWithSharedMemory(std::move(ro_mapping));
|
||||
|
||||
const ProcessId pid = analyzer->GetFirstProcess();
|
||||
ASSERT_NE(ProcessId{0}, pid);
|
||||
const ActivityUserData::Snapshot& data_snapshot =
|
||||
analyzer->GetProcessDataSnapshot(pid);
|
||||
ASSERT_LE(1U, data_snapshot.size());
|
||||
EXPECT_EQ("bar", data_snapshot.at("foo").GetString());
|
||||
}
|
||||
|
||||
TEST_F(ActivityAnalyzerTest, UserDataSnapshotTest) {
|
||||
GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0);
|
||||
ThreadActivityAnalyzer::Snapshot tracker_snapshot;
|
||||
|
||||
const char string1a[] = "string1a";
|
||||
const char string1b[] = "string1b";
|
||||
const char string2a[] = "string2a";
|
||||
const char string2b[] = "string2b";
|
||||
|
||||
PersistentMemoryAllocator* allocator =
|
||||
GlobalActivityTracker::Get()->allocator();
|
||||
GlobalActivityAnalyzer global_analyzer(
|
||||
std::make_unique<PersistentMemoryAllocator>(
|
||||
const_cast<void*>(allocator->data()), allocator->size(), 0, 0, "",
|
||||
true));
|
||||
|
||||
ThreadActivityTracker* tracker =
|
||||
GlobalActivityTracker::Get()->GetOrCreateTrackerForCurrentThread();
|
||||
|
||||
{
|
||||
ScopedActivity activity1(1, 11, 111);
|
||||
ActivityUserData& user_data1 = activity1.user_data();
|
||||
user_data1.Set("raw1", "foo1", 4);
|
||||
user_data1.SetString("string1", "bar1");
|
||||
user_data1.SetChar("char1", '1');
|
||||
user_data1.SetInt("int1", -1111);
|
||||
user_data1.SetUint("uint1", 1111);
|
||||
user_data1.SetBool("bool1", true);
|
||||
user_data1.SetReference("ref1", string1a, sizeof(string1a));
|
||||
user_data1.SetStringReference("sref1", string1b);
|
||||
|
||||
{
|
||||
ScopedActivity activity2(2, 22, 222);
|
||||
ActivityUserData& user_data2 = activity2.user_data();
|
||||
user_data2.Set("raw2", "foo2", 4);
|
||||
user_data2.SetString("string2", "bar2");
|
||||
user_data2.SetChar("char2", '2');
|
||||
user_data2.SetInt("int2", -2222);
|
||||
user_data2.SetUint("uint2", 2222);
|
||||
user_data2.SetBool("bool2", false);
|
||||
user_data2.SetReference("ref2", string2a, sizeof(string2a));
|
||||
user_data2.SetStringReference("sref2", string2b);
|
||||
|
||||
ASSERT_TRUE(tracker->CreateSnapshot(&tracker_snapshot));
|
||||
ASSERT_EQ(2U, tracker_snapshot.activity_stack.size());
|
||||
|
||||
ThreadActivityAnalyzer analyzer(*tracker);
|
||||
analyzer.AddGlobalInformation(&global_analyzer);
|
||||
const ThreadActivityAnalyzer::Snapshot& analyzer_snapshot =
|
||||
analyzer.activity_snapshot();
|
||||
ASSERT_EQ(2U, analyzer_snapshot.user_data_stack.size());
|
||||
const ActivityUserData::Snapshot& user_data =
|
||||
analyzer_snapshot.user_data_stack.at(1);
|
||||
EXPECT_EQ(8U, user_data.size());
|
||||
ASSERT_TRUE(Contains(user_data, "raw2"));
|
||||
EXPECT_EQ("foo2", user_data.at("raw2").Get());
|
||||
ASSERT_TRUE(Contains(user_data, "string2"));
|
||||
EXPECT_EQ("bar2", user_data.at("string2").GetString());
|
||||
ASSERT_TRUE(Contains(user_data, "char2"));
|
||||
EXPECT_EQ('2', user_data.at("char2").GetChar());
|
||||
ASSERT_TRUE(Contains(user_data, "int2"));
|
||||
EXPECT_EQ(-2222, user_data.at("int2").GetInt());
|
||||
ASSERT_TRUE(Contains(user_data, "uint2"));
|
||||
EXPECT_EQ(2222U, user_data.at("uint2").GetUint());
|
||||
ASSERT_TRUE(Contains(user_data, "bool2"));
|
||||
EXPECT_FALSE(user_data.at("bool2").GetBool());
|
||||
ASSERT_TRUE(Contains(user_data, "ref2"));
|
||||
EXPECT_EQ(string2a, user_data.at("ref2").GetReference().data());
|
||||
EXPECT_EQ(sizeof(string2a), user_data.at("ref2").GetReference().size());
|
||||
ASSERT_TRUE(Contains(user_data, "sref2"));
|
||||
EXPECT_EQ(string2b, user_data.at("sref2").GetStringReference().data());
|
||||
EXPECT_EQ(strlen(string2b),
|
||||
user_data.at("sref2").GetStringReference().size());
|
||||
}
|
||||
|
||||
ASSERT_TRUE(tracker->CreateSnapshot(&tracker_snapshot));
|
||||
ASSERT_EQ(1U, tracker_snapshot.activity_stack.size());
|
||||
|
||||
ThreadActivityAnalyzer analyzer(*tracker);
|
||||
analyzer.AddGlobalInformation(&global_analyzer);
|
||||
const ThreadActivityAnalyzer::Snapshot& analyzer_snapshot =
|
||||
analyzer.activity_snapshot();
|
||||
ASSERT_EQ(1U, analyzer_snapshot.user_data_stack.size());
|
||||
const ActivityUserData::Snapshot& user_data =
|
||||
analyzer_snapshot.user_data_stack.at(0);
|
||||
EXPECT_EQ(8U, user_data.size());
|
||||
EXPECT_EQ("foo1", user_data.at("raw1").Get());
|
||||
EXPECT_EQ("bar1", user_data.at("string1").GetString());
|
||||
EXPECT_EQ('1', user_data.at("char1").GetChar());
|
||||
EXPECT_EQ(-1111, user_data.at("int1").GetInt());
|
||||
EXPECT_EQ(1111U, user_data.at("uint1").GetUint());
|
||||
EXPECT_TRUE(user_data.at("bool1").GetBool());
|
||||
EXPECT_EQ(string1a, user_data.at("ref1").GetReference().data());
|
||||
EXPECT_EQ(sizeof(string1a), user_data.at("ref1").GetReference().size());
|
||||
EXPECT_EQ(string1b, user_data.at("sref1").GetStringReference().data());
|
||||
EXPECT_EQ(strlen(string1b),
|
||||
user_data.at("sref1").GetStringReference().size());
|
||||
}
|
||||
|
||||
ASSERT_TRUE(tracker->CreateSnapshot(&tracker_snapshot));
|
||||
ASSERT_EQ(0U, tracker_snapshot.activity_stack.size());
|
||||
}
|
||||
|
||||
TEST_F(ActivityAnalyzerTest, GlobalUserDataTest) {
|
||||
const ProcessId pid = GetCurrentProcId();
|
||||
GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0);
|
||||
|
||||
const char string1[] = "foo";
|
||||
const char string2[] = "bar";
|
||||
|
||||
PersistentMemoryAllocator* allocator =
|
||||
GlobalActivityTracker::Get()->allocator();
|
||||
GlobalActivityAnalyzer global_analyzer(
|
||||
std::make_unique<PersistentMemoryAllocator>(
|
||||
const_cast<void*>(allocator->data()), allocator->size(), 0, 0, "",
|
||||
true));
|
||||
|
||||
ActivityUserData& process_data = GlobalActivityTracker::Get()->process_data();
|
||||
ASSERT_NE(0U, process_data.id());
|
||||
process_data.Set("raw", "foo", 3);
|
||||
process_data.SetString("string", "bar");
|
||||
process_data.SetBool("bool1", false);
|
||||
process_data.SetBool("bool2", false)->store(true, std::memory_order_relaxed);
|
||||
process_data.SetChar("char1", '7');
|
||||
process_data.SetChar("char2", '8')->store('9', std::memory_order_relaxed);
|
||||
process_data.SetInt("int", -9998)->fetch_sub(1, std::memory_order_relaxed);
|
||||
process_data.SetUint("uint", 9998)->fetch_add(1, std::memory_order_relaxed);
|
||||
process_data.SetReference("ref", string1, sizeof(string1));
|
||||
process_data.SetStringReference("sref", string2);
|
||||
|
||||
ProcessId first_pid = global_analyzer.GetFirstProcess();
|
||||
DCHECK_EQ(pid, first_pid);
|
||||
const ActivityUserData::Snapshot& snapshot =
|
||||
global_analyzer.GetProcessDataSnapshot(pid);
|
||||
ASSERT_TRUE(Contains(snapshot, "raw"));
|
||||
EXPECT_EQ("foo", snapshot.at("raw").Get());
|
||||
ASSERT_TRUE(Contains(snapshot, "string"));
|
||||
EXPECT_EQ("bar", snapshot.at("string").GetString());
|
||||
ASSERT_TRUE(Contains(snapshot, "bool1"));
|
||||
EXPECT_FALSE(snapshot.at("bool1").GetBool());
|
||||
ASSERT_TRUE(Contains(snapshot, "bool2"));
|
||||
EXPECT_TRUE(snapshot.at("bool2").GetBool());
|
||||
ASSERT_TRUE(Contains(snapshot, "char1"));
|
||||
EXPECT_EQ('7', snapshot.at("char1").GetChar());
|
||||
ASSERT_TRUE(Contains(snapshot, "char2"));
|
||||
EXPECT_EQ('9', snapshot.at("char2").GetChar());
|
||||
ASSERT_TRUE(Contains(snapshot, "int"));
|
||||
EXPECT_EQ(-9999, snapshot.at("int").GetInt());
|
||||
ASSERT_TRUE(Contains(snapshot, "uint"));
|
||||
EXPECT_EQ(9999U, snapshot.at("uint").GetUint());
|
||||
ASSERT_TRUE(Contains(snapshot, "ref"));
|
||||
EXPECT_EQ(string1, snapshot.at("ref").GetReference().data());
|
||||
EXPECT_EQ(sizeof(string1), snapshot.at("ref").GetReference().size());
|
||||
ASSERT_TRUE(Contains(snapshot, "sref"));
|
||||
EXPECT_EQ(string2, snapshot.at("sref").GetStringReference().data());
|
||||
EXPECT_EQ(strlen(string2), snapshot.at("sref").GetStringReference().size());
|
||||
}
|
||||
|
||||
TEST_F(ActivityAnalyzerTest, GlobalModulesTest) {
|
||||
GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0);
|
||||
GlobalActivityTracker* global = GlobalActivityTracker::Get();
|
||||
|
||||
PersistentMemoryAllocator* allocator = global->allocator();
|
||||
GlobalActivityAnalyzer global_analyzer(
|
||||
std::make_unique<PersistentMemoryAllocator>(
|
||||
const_cast<void*>(allocator->data()), allocator->size(), 0, 0, "",
|
||||
true));
|
||||
|
||||
GlobalActivityTracker::ModuleInfo info1;
|
||||
info1.is_loaded = true;
|
||||
info1.address = 0x12345678;
|
||||
info1.load_time = 1111;
|
||||
info1.size = 0xABCDEF;
|
||||
info1.timestamp = 111;
|
||||
info1.age = 11;
|
||||
info1.identifier[0] = 1;
|
||||
info1.file = "anything";
|
||||
info1.debug_file = "elsewhere";
|
||||
|
||||
global->RecordModuleInfo(info1);
|
||||
std::vector<GlobalActivityTracker::ModuleInfo> modules1;
|
||||
modules1 = global_analyzer.GetModules(global_analyzer.GetFirstProcess());
|
||||
ASSERT_EQ(1U, modules1.size());
|
||||
GlobalActivityTracker::ModuleInfo& stored1a = modules1[0];
|
||||
EXPECT_EQ(info1.is_loaded, stored1a.is_loaded);
|
||||
EXPECT_EQ(info1.address, stored1a.address);
|
||||
EXPECT_NE(info1.load_time, stored1a.load_time);
|
||||
EXPECT_EQ(info1.size, stored1a.size);
|
||||
EXPECT_EQ(info1.timestamp, stored1a.timestamp);
|
||||
EXPECT_EQ(info1.age, stored1a.age);
|
||||
EXPECT_EQ(info1.identifier[0], stored1a.identifier[0]);
|
||||
EXPECT_EQ(info1.file, stored1a.file);
|
||||
EXPECT_EQ(info1.debug_file, stored1a.debug_file);
|
||||
|
||||
info1.is_loaded = false;
|
||||
global->RecordModuleInfo(info1);
|
||||
modules1 = global_analyzer.GetModules(global_analyzer.GetFirstProcess());
|
||||
ASSERT_EQ(1U, modules1.size());
|
||||
GlobalActivityTracker::ModuleInfo& stored1b = modules1[0];
|
||||
EXPECT_EQ(info1.is_loaded, stored1b.is_loaded);
|
||||
EXPECT_EQ(info1.address, stored1b.address);
|
||||
EXPECT_NE(info1.load_time, stored1b.load_time);
|
||||
EXPECT_EQ(info1.size, stored1b.size);
|
||||
EXPECT_EQ(info1.timestamp, stored1b.timestamp);
|
||||
EXPECT_EQ(info1.age, stored1b.age);
|
||||
EXPECT_EQ(info1.identifier[0], stored1b.identifier[0]);
|
||||
EXPECT_EQ(info1.file, stored1b.file);
|
||||
EXPECT_EQ(info1.debug_file, stored1b.debug_file);
|
||||
|
||||
GlobalActivityTracker::ModuleInfo info2;
|
||||
info2.is_loaded = true;
|
||||
info2.address = 0x87654321;
|
||||
info2.load_time = 2222;
|
||||
info2.size = 0xFEDCBA;
|
||||
info2.timestamp = 222;
|
||||
info2.age = 22;
|
||||
info2.identifier[0] = 2;
|
||||
info2.file = "nothing";
|
||||
info2.debug_file = "farewell";
|
||||
|
||||
global->RecordModuleInfo(info2);
|
||||
std::vector<GlobalActivityTracker::ModuleInfo> modules2;
|
||||
modules2 = global_analyzer.GetModules(global_analyzer.GetFirstProcess());
|
||||
ASSERT_EQ(2U, modules2.size());
|
||||
GlobalActivityTracker::ModuleInfo& stored2 = modules2[1];
|
||||
EXPECT_EQ(info2.is_loaded, stored2.is_loaded);
|
||||
EXPECT_EQ(info2.address, stored2.address);
|
||||
EXPECT_NE(info2.load_time, stored2.load_time);
|
||||
EXPECT_EQ(info2.size, stored2.size);
|
||||
EXPECT_EQ(info2.timestamp, stored2.timestamp);
|
||||
EXPECT_EQ(info2.age, stored2.age);
|
||||
EXPECT_EQ(info2.identifier[0], stored2.identifier[0]);
|
||||
EXPECT_EQ(info2.file, stored2.file);
|
||||
EXPECT_EQ(info2.debug_file, stored2.debug_file);
|
||||
}
|
||||
|
||||
TEST_F(ActivityAnalyzerTest, GlobalLogMessages) {
|
||||
GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0);
|
||||
|
||||
PersistentMemoryAllocator* allocator =
|
||||
GlobalActivityTracker::Get()->allocator();
|
||||
GlobalActivityAnalyzer analyzer(std::make_unique<PersistentMemoryAllocator>(
|
||||
const_cast<void*>(allocator->data()), allocator->size(), 0, 0, "", true));
|
||||
|
||||
GlobalActivityTracker::Get()->RecordLogMessage("hello world");
|
||||
GlobalActivityTracker::Get()->RecordLogMessage("foo bar");
|
||||
|
||||
std::vector<std::string> messages = analyzer.GetLogMessages();
|
||||
ASSERT_EQ(2U, messages.size());
|
||||
EXPECT_EQ("hello world", messages[0]);
|
||||
EXPECT_EQ("foo bar", messages[1]);
|
||||
}
|
||||
|
||||
TEST_F(ActivityAnalyzerTest, GlobalMultiProcess) {
|
||||
constexpr ProcessId kProcessIdA = 1001;
|
||||
constexpr ProcessId kProcessIdB = 2002;
|
||||
GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3,
|
||||
kProcessIdA);
|
||||
GlobalActivityTracker* global = GlobalActivityTracker::Get();
|
||||
PersistentMemoryAllocator* allocator = global->allocator();
|
||||
EXPECT_EQ(kProcessIdA, global->process_id());
|
||||
|
||||
ProcessId process_id;
|
||||
int64_t create_stamp;
|
||||
ActivityUserData::GetOwningProcessId(
|
||||
GlobalActivityTracker::Get()->process_data().GetBaseAddress(),
|
||||
&process_id, &create_stamp);
|
||||
ASSERT_EQ(kProcessIdA, process_id);
|
||||
|
||||
GlobalActivityTracker::Get()->process_data().SetInt("pid",
|
||||
global->process_id());
|
||||
|
||||
GlobalActivityAnalyzer analyzer(std::make_unique<PersistentMemoryAllocator>(
|
||||
const_cast<void*>(allocator->data()), allocator->size(), 0, 0, "", true));
|
||||
|
||||
AsOtherProcess(kProcessIdB, [&global, kProcessIdB]() {
|
||||
ASSERT_NE(global, GlobalActivityTracker::Get());
|
||||
EXPECT_EQ(kProcessIdB, GlobalActivityTracker::Get()->process_id());
|
||||
|
||||
ProcessId process_id;
|
||||
int64_t create_stamp;
|
||||
ActivityUserData::GetOwningProcessId(
|
||||
GlobalActivityTracker::Get()->process_data().GetBaseAddress(),
|
||||
&process_id, &create_stamp);
|
||||
ASSERT_EQ(kProcessIdB, process_id);
|
||||
|
||||
GlobalActivityTracker::Get()->process_data().SetInt(
|
||||
"pid", GlobalActivityTracker::Get()->process_id());
|
||||
});
|
||||
ASSERT_EQ(global, GlobalActivityTracker::Get());
|
||||
EXPECT_EQ(kProcessIdA, GlobalActivityTracker::Get()->process_id());
|
||||
|
||||
const ProcessId pid1 = analyzer.GetFirstProcess();
|
||||
ASSERT_EQ(kProcessIdA, pid1);
|
||||
const ProcessId pid2 = analyzer.GetNextProcess();
|
||||
ASSERT_EQ(kProcessIdB, pid2);
|
||||
EXPECT_EQ(ProcessId{0}, analyzer.GetNextProcess());
|
||||
|
||||
const ActivityUserData::Snapshot& pdata1 =
|
||||
analyzer.GetProcessDataSnapshot(pid1);
|
||||
const ActivityUserData::Snapshot& pdata2 =
|
||||
analyzer.GetProcessDataSnapshot(pid2);
|
||||
EXPECT_EQ(kProcessIdA, static_cast<ProcessId>(pdata1.at("pid").GetInt()));
|
||||
EXPECT_EQ(kProcessIdB, static_cast<ProcessId>(pdata2.at("pid").GetInt()));
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,593 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/debug/activity_tracker.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "base/files/file.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/files/memory_mapped_file.h"
|
||||
#include "base/files/scoped_temp_dir.h"
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/functional/callback_helpers.h"
|
||||
#include "base/memory/ptr_util.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/pending_task.h"
|
||||
#include "base/rand_util.h"
|
||||
#include "base/synchronization/condition_variable.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "base/test/spin_wait.h"
|
||||
#include "base/threading/platform_thread.h"
|
||||
#include "base/threading/simple_thread.h"
|
||||
#include "base/time/time.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
namespace {
|
||||
|
||||
class TestActivityTracker {
|
||||
public:
|
||||
TestActivityTracker(std::unique_ptr<char[]> memory, size_t mem_size)
|
||||
: mem_segment_(std::move(memory)),
|
||||
tracker_(memset(mem_segment_.get(), 0, mem_size), mem_size) {}
|
||||
|
||||
~TestActivityTracker() = default;
|
||||
|
||||
ThreadActivityTracker& tracker() { return tracker_; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<char[]> mem_segment_; // Must outlive `tracker_`
|
||||
ThreadActivityTracker tracker_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
class ActivityTrackerTest : public testing::Test {
|
||||
public:
|
||||
const int kMemorySize = 1 << 20; // 1MiB
|
||||
const int kStackSize = 1 << 10; // 1KiB
|
||||
|
||||
using ActivityId = ThreadActivityTracker::ActivityId;
|
||||
|
||||
ActivityTrackerTest() = default;
|
||||
|
||||
~ActivityTrackerTest() override {
|
||||
GlobalActivityTracker* global_tracker = GlobalActivityTracker::Get();
|
||||
if (global_tracker) {
|
||||
global_tracker->ReleaseTrackerForCurrentThreadForTesting();
|
||||
delete global_tracker;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<TestActivityTracker> CreateActivityTracker() {
|
||||
std::unique_ptr<char[]> memory(new char[kStackSize]);
|
||||
return std::make_unique<TestActivityTracker>(std::move(memory), kStackSize);
|
||||
}
|
||||
|
||||
size_t GetGlobalActiveTrackerCount() {
|
||||
GlobalActivityTracker* global_tracker = GlobalActivityTracker::Get();
|
||||
if (!global_tracker)
|
||||
return 0;
|
||||
return global_tracker->thread_tracker_count_.load(
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
size_t GetGlobalInactiveTrackerCount() {
|
||||
GlobalActivityTracker* global_tracker = GlobalActivityTracker::Get();
|
||||
if (!global_tracker)
|
||||
return 0;
|
||||
AutoLock autolock(global_tracker->thread_tracker_allocator_lock_);
|
||||
return global_tracker->thread_tracker_allocator_.cache_used();
|
||||
}
|
||||
|
||||
size_t GetGlobalUserDataMemoryCacheUsed() {
|
||||
AutoLock autolock(GlobalActivityTracker::Get()->user_data_allocator_lock_);
|
||||
return GlobalActivityTracker::Get()->user_data_allocator_.cache_used();
|
||||
}
|
||||
|
||||
void HandleProcessExit(ProcessId id,
|
||||
int64_t stamp,
|
||||
int code,
|
||||
GlobalActivityTracker::ProcessPhase phase,
|
||||
std::string&& command,
|
||||
ActivityUserData::Snapshot&& data) {
|
||||
exit_id_ = id;
|
||||
exit_stamp_ = stamp;
|
||||
exit_code_ = code;
|
||||
exit_phase_ = phase;
|
||||
exit_command_ = std::move(command);
|
||||
exit_data_ = std::move(data);
|
||||
}
|
||||
|
||||
ProcessId exit_id_ = 0;
|
||||
int64_t exit_stamp_;
|
||||
int exit_code_;
|
||||
GlobalActivityTracker::ProcessPhase exit_phase_;
|
||||
std::string exit_command_;
|
||||
ActivityUserData::Snapshot exit_data_;
|
||||
};
|
||||
|
||||
TEST_F(ActivityTrackerTest, UserDataTest) {
|
||||
char buffer[256];
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
ActivityUserData data(buffer, sizeof(buffer));
|
||||
size_t space = sizeof(buffer) - sizeof(ActivityUserData::MemoryHeader);
|
||||
ASSERT_EQ(space, data.memory_.size());
|
||||
|
||||
data.SetInt("foo", 1);
|
||||
space -= 24;
|
||||
ASSERT_EQ(space, data.memory_.size());
|
||||
|
||||
data.SetUint("b", 1U); // Small names fit beside header in a word.
|
||||
space -= 16;
|
||||
ASSERT_EQ(space, data.memory_.size());
|
||||
|
||||
data.Set("c", buffer, 10);
|
||||
space -= 24;
|
||||
ASSERT_EQ(space, data.memory_.size());
|
||||
|
||||
data.SetString("dear john", "it's been fun");
|
||||
space -= 32;
|
||||
ASSERT_EQ(space, data.memory_.size());
|
||||
|
||||
data.Set("c", buffer, 20);
|
||||
ASSERT_EQ(space, data.memory_.size());
|
||||
|
||||
data.SetString("dear john", "but we're done together");
|
||||
ASSERT_EQ(space, data.memory_.size());
|
||||
|
||||
data.SetString("dear john", "bye");
|
||||
ASSERT_EQ(space, data.memory_.size());
|
||||
|
||||
data.SetChar("d", 'x');
|
||||
space -= 8;
|
||||
ASSERT_EQ(space, data.memory_.size());
|
||||
|
||||
data.SetBool("ee", true);
|
||||
space -= 16;
|
||||
ASSERT_EQ(space, data.memory_.size());
|
||||
|
||||
data.SetString("f", "");
|
||||
space -= 8;
|
||||
ASSERT_EQ(space, data.memory_.size());
|
||||
}
|
||||
|
||||
TEST_F(ActivityTrackerTest, PushPopTest) {
|
||||
std::unique_ptr<TestActivityTracker> tracker = CreateActivityTracker();
|
||||
ThreadActivityTracker::Snapshot snapshot;
|
||||
|
||||
ASSERT_TRUE(tracker->tracker().CreateSnapshot(&snapshot));
|
||||
ASSERT_EQ(0U, snapshot.activity_stack_depth);
|
||||
ASSERT_EQ(0U, snapshot.activity_stack.size());
|
||||
|
||||
char origin1;
|
||||
ActivityId id1 = tracker->tracker().PushActivity(&origin1, Activity::ACT_TASK,
|
||||
ActivityData::ForTask(11));
|
||||
ASSERT_TRUE(tracker->tracker().CreateSnapshot(&snapshot));
|
||||
ASSERT_EQ(1U, snapshot.activity_stack_depth);
|
||||
ASSERT_EQ(1U, snapshot.activity_stack.size());
|
||||
EXPECT_NE(0, snapshot.activity_stack[0].time_internal);
|
||||
EXPECT_EQ(Activity::ACT_TASK, snapshot.activity_stack[0].activity_type);
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(&origin1),
|
||||
snapshot.activity_stack[0].origin_address);
|
||||
EXPECT_EQ(11U, snapshot.activity_stack[0].data.task.sequence_id);
|
||||
|
||||
char origin2;
|
||||
char lock2;
|
||||
ActivityId id2 = tracker->tracker().PushActivity(
|
||||
&origin2, Activity::ACT_LOCK, ActivityData::ForLock(&lock2));
|
||||
ASSERT_TRUE(tracker->tracker().CreateSnapshot(&snapshot));
|
||||
ASSERT_EQ(2U, snapshot.activity_stack_depth);
|
||||
ASSERT_EQ(2U, snapshot.activity_stack.size());
|
||||
EXPECT_LE(snapshot.activity_stack[0].time_internal,
|
||||
snapshot.activity_stack[1].time_internal);
|
||||
EXPECT_EQ(Activity::ACT_LOCK, snapshot.activity_stack[1].activity_type);
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(&origin2),
|
||||
snapshot.activity_stack[1].origin_address);
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(&lock2),
|
||||
snapshot.activity_stack[1].data.lock.lock_address);
|
||||
|
||||
tracker->tracker().PopActivity(id2);
|
||||
ASSERT_TRUE(tracker->tracker().CreateSnapshot(&snapshot));
|
||||
ASSERT_EQ(1U, snapshot.activity_stack_depth);
|
||||
ASSERT_EQ(1U, snapshot.activity_stack.size());
|
||||
EXPECT_EQ(Activity::ACT_TASK, snapshot.activity_stack[0].activity_type);
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(&origin1),
|
||||
snapshot.activity_stack[0].origin_address);
|
||||
EXPECT_EQ(11U, snapshot.activity_stack[0].data.task.sequence_id);
|
||||
|
||||
tracker->tracker().PopActivity(id1);
|
||||
ASSERT_TRUE(tracker->tracker().CreateSnapshot(&snapshot));
|
||||
ASSERT_EQ(0U, snapshot.activity_stack_depth);
|
||||
ASSERT_EQ(0U, snapshot.activity_stack.size());
|
||||
}
|
||||
|
||||
TEST_F(ActivityTrackerTest, ScopedTaskTest) {
|
||||
GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0);
|
||||
|
||||
ThreadActivityTracker* tracker =
|
||||
GlobalActivityTracker::Get()->GetOrCreateTrackerForCurrentThread();
|
||||
ThreadActivityTracker::Snapshot snapshot;
|
||||
ASSERT_EQ(0U, GetGlobalUserDataMemoryCacheUsed());
|
||||
|
||||
ASSERT_TRUE(tracker->CreateSnapshot(&snapshot));
|
||||
ASSERT_EQ(0U, snapshot.activity_stack_depth);
|
||||
ASSERT_EQ(0U, snapshot.activity_stack.size());
|
||||
|
||||
{
|
||||
PendingTask task1(FROM_HERE, DoNothing());
|
||||
ScopedTaskRunActivity activity1(task1);
|
||||
[[maybe_unused]] ActivityUserData& user_data1 = activity1.user_data();
|
||||
EXPECT_TRUE(activity1.IsRecorded());
|
||||
|
||||
ASSERT_TRUE(tracker->CreateSnapshot(&snapshot));
|
||||
ASSERT_EQ(1U, snapshot.activity_stack_depth);
|
||||
ASSERT_EQ(1U, snapshot.activity_stack.size());
|
||||
EXPECT_EQ(Activity::ACT_TASK, snapshot.activity_stack[0].activity_type);
|
||||
|
||||
{
|
||||
PendingTask task2(FROM_HERE, DoNothing());
|
||||
ScopedTaskRunActivity activity2(task2);
|
||||
[[maybe_unused]] ActivityUserData& user_data2 = activity2.user_data();
|
||||
|
||||
ASSERT_TRUE(tracker->CreateSnapshot(&snapshot));
|
||||
ASSERT_EQ(2U, snapshot.activity_stack_depth);
|
||||
ASSERT_EQ(2U, snapshot.activity_stack.size());
|
||||
EXPECT_EQ(Activity::ACT_TASK, snapshot.activity_stack[1].activity_type);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(tracker->CreateSnapshot(&snapshot));
|
||||
ASSERT_EQ(1U, snapshot.activity_stack_depth);
|
||||
ASSERT_EQ(1U, snapshot.activity_stack.size());
|
||||
EXPECT_EQ(Activity::ACT_TASK, snapshot.activity_stack[0].activity_type);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(tracker->CreateSnapshot(&snapshot));
|
||||
ASSERT_EQ(0U, snapshot.activity_stack_depth);
|
||||
ASSERT_EQ(0U, snapshot.activity_stack.size());
|
||||
ASSERT_EQ(2U, GetGlobalUserDataMemoryCacheUsed());
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class SimpleLockThread : public SimpleThread {
|
||||
public:
|
||||
SimpleLockThread(const std::string& name, Lock* lock)
|
||||
: SimpleThread(name, Options()),
|
||||
lock_(lock),
|
||||
data_changed_(false),
|
||||
is_running_(false) {}
|
||||
|
||||
SimpleLockThread(const SimpleLockThread&) = delete;
|
||||
SimpleLockThread& operator=(const SimpleLockThread&) = delete;
|
||||
|
||||
~SimpleLockThread() override = default;
|
||||
|
||||
void Run() override {
|
||||
ThreadActivityTracker* tracker =
|
||||
GlobalActivityTracker::Get()->GetOrCreateTrackerForCurrentThread();
|
||||
uint32_t pre_version = tracker->GetDataVersionForTesting();
|
||||
|
||||
is_running_.store(true, std::memory_order_relaxed);
|
||||
lock_->Acquire();
|
||||
data_changed_ = tracker->GetDataVersionForTesting() != pre_version;
|
||||
lock_->Release();
|
||||
is_running_.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
bool IsRunning() { return is_running_.load(std::memory_order_relaxed); }
|
||||
|
||||
bool WasDataChanged() { return data_changed_; }
|
||||
|
||||
private:
|
||||
raw_ptr<Lock> lock_;
|
||||
bool data_changed_;
|
||||
std::atomic<bool> is_running_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(ActivityTrackerTest, LockTest) {
|
||||
GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0);
|
||||
|
||||
ThreadActivityTracker* tracker =
|
||||
GlobalActivityTracker::Get()->GetOrCreateTrackerForCurrentThread();
|
||||
ThreadActivityTracker::Snapshot snapshot;
|
||||
ASSERT_EQ(0U, GetGlobalUserDataMemoryCacheUsed());
|
||||
|
||||
Lock lock;
|
||||
uint32_t pre_version = tracker->GetDataVersionForTesting();
|
||||
|
||||
// Check no activity when only "trying" a lock.
|
||||
EXPECT_TRUE(lock.Try());
|
||||
EXPECT_EQ(pre_version, tracker->GetDataVersionForTesting());
|
||||
lock.AssertAcquired();
|
||||
lock.Release();
|
||||
EXPECT_EQ(pre_version, tracker->GetDataVersionForTesting());
|
||||
|
||||
// Check no activity when acquiring a free lock.
|
||||
SimpleLockThread t1("locker1", &lock);
|
||||
t1.Start();
|
||||
t1.Join();
|
||||
EXPECT_FALSE(t1.WasDataChanged());
|
||||
|
||||
// Check that activity is recorded when acquring a busy lock.
|
||||
SimpleLockThread t2("locker2", &lock);
|
||||
lock.Acquire();
|
||||
t2.Start();
|
||||
while (!t2.IsRunning())
|
||||
PlatformThread::Sleep(Milliseconds(10));
|
||||
// t2 can't join until the lock is released but have to give time for t2 to
|
||||
// actually block on the lock before releasing it or the results will not
|
||||
// be correct.
|
||||
PlatformThread::Sleep(Milliseconds(200));
|
||||
lock.Release();
|
||||
// Now the results will be valid.
|
||||
t2.Join();
|
||||
EXPECT_TRUE(t2.WasDataChanged());
|
||||
}
|
||||
|
||||
TEST_F(ActivityTrackerTest, ExceptionTest) {
|
||||
GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0);
|
||||
GlobalActivityTracker* global = GlobalActivityTracker::Get();
|
||||
|
||||
ThreadActivityTracker* tracker =
|
||||
GlobalActivityTracker::Get()->GetOrCreateTrackerForCurrentThread();
|
||||
ThreadActivityTracker::Snapshot snapshot;
|
||||
ASSERT_EQ(0U, GetGlobalUserDataMemoryCacheUsed());
|
||||
|
||||
ASSERT_TRUE(tracker->CreateSnapshot(&snapshot));
|
||||
ASSERT_EQ(0U, snapshot.last_exception.activity_type);
|
||||
|
||||
char origin;
|
||||
global->RecordException(&origin, 42);
|
||||
|
||||
ASSERT_TRUE(tracker->CreateSnapshot(&snapshot));
|
||||
EXPECT_EQ(Activity::ACT_EXCEPTION, snapshot.last_exception.activity_type);
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(&origin),
|
||||
snapshot.last_exception.origin_address);
|
||||
EXPECT_EQ(42U, snapshot.last_exception.data.exception.code);
|
||||
}
|
||||
|
||||
TEST_F(ActivityTrackerTest, CreateWithFileTest) {
|
||||
const char temp_name[] = "CreateWithFileTest";
|
||||
ScopedTempDir temp_dir;
|
||||
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
|
||||
FilePath temp_file = temp_dir.GetPath().AppendASCII(temp_name);
|
||||
const size_t temp_size = 64 << 10; // 64 KiB
|
||||
|
||||
// Create a global tracker on a new file.
|
||||
ASSERT_FALSE(PathExists(temp_file));
|
||||
GlobalActivityTracker::CreateWithFile(temp_file, temp_size, 0, "foo", 3);
|
||||
GlobalActivityTracker* global = GlobalActivityTracker::Get();
|
||||
EXPECT_EQ(std::string("foo"), global->allocator()->Name());
|
||||
global->ReleaseTrackerForCurrentThreadForTesting();
|
||||
delete global;
|
||||
|
||||
// Create a global tracker over an existing file, replacing it. If the
|
||||
// replacement doesn't work, the name will remain as it was first created.
|
||||
ASSERT_TRUE(PathExists(temp_file));
|
||||
GlobalActivityTracker::CreateWithFile(temp_file, temp_size, 0, "bar", 3);
|
||||
global = GlobalActivityTracker::Get();
|
||||
EXPECT_EQ(std::string("bar"), global->allocator()->Name());
|
||||
global->ReleaseTrackerForCurrentThreadForTesting();
|
||||
delete global;
|
||||
}
|
||||
|
||||
|
||||
// GlobalActivityTracker tests below.
|
||||
|
||||
TEST_F(ActivityTrackerTest, BasicTest) {
|
||||
GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0);
|
||||
GlobalActivityTracker* global = GlobalActivityTracker::Get();
|
||||
|
||||
// Ensure the data repositories have backing store, indicated by non-zero ID.
|
||||
EXPECT_NE(0U, global->process_data().id());
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class SimpleActivityThread : public SimpleThread {
|
||||
public:
|
||||
SimpleActivityThread(const std::string& name,
|
||||
const void* origin,
|
||||
Activity::Type activity,
|
||||
const ActivityData& data)
|
||||
: SimpleThread(name, Options()),
|
||||
origin_(origin),
|
||||
activity_(activity),
|
||||
data_(data),
|
||||
ready_(false),
|
||||
exit_(false),
|
||||
exit_condition_(&lock_) {}
|
||||
|
||||
SimpleActivityThread(const SimpleActivityThread&) = delete;
|
||||
SimpleActivityThread& operator=(const SimpleActivityThread&) = delete;
|
||||
|
||||
~SimpleActivityThread() override = default;
|
||||
|
||||
void Run() override {
|
||||
ThreadActivityTracker::ActivityId id =
|
||||
GlobalActivityTracker::Get()
|
||||
->GetOrCreateTrackerForCurrentThread()
|
||||
->PushActivity(origin_, activity_, data_);
|
||||
|
||||
{
|
||||
AutoLock auto_lock(lock_);
|
||||
ready_.store(true, std::memory_order_release);
|
||||
while (!exit_.load(std::memory_order_relaxed))
|
||||
exit_condition_.Wait();
|
||||
}
|
||||
|
||||
GlobalActivityTracker::Get()->GetTrackerForCurrentThread()->PopActivity(id);
|
||||
}
|
||||
|
||||
void Exit() {
|
||||
AutoLock auto_lock(lock_);
|
||||
exit_.store(true, std::memory_order_relaxed);
|
||||
exit_condition_.Signal();
|
||||
}
|
||||
|
||||
void WaitReady() {
|
||||
SPIN_FOR_1_SECOND_OR_UNTIL_TRUE(ready_.load(std::memory_order_acquire));
|
||||
}
|
||||
|
||||
private:
|
||||
raw_ptr<const void> origin_;
|
||||
Activity::Type activity_;
|
||||
ActivityData data_;
|
||||
|
||||
std::atomic<bool> ready_;
|
||||
std::atomic<bool> exit_;
|
||||
Lock lock_;
|
||||
ConditionVariable exit_condition_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(ActivityTrackerTest, ThreadDeathTest) {
|
||||
GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0);
|
||||
GlobalActivityTracker::Get()->GetOrCreateTrackerForCurrentThread();
|
||||
const size_t starting_active = GetGlobalActiveTrackerCount();
|
||||
const size_t starting_inactive = GetGlobalInactiveTrackerCount();
|
||||
|
||||
SimpleActivityThread t1("t1", nullptr, Activity::ACT_TASK,
|
||||
ActivityData::ForTask(11));
|
||||
t1.Start();
|
||||
t1.WaitReady();
|
||||
EXPECT_EQ(starting_active + 1, GetGlobalActiveTrackerCount());
|
||||
EXPECT_EQ(starting_inactive, GetGlobalInactiveTrackerCount());
|
||||
|
||||
t1.Exit();
|
||||
t1.Join();
|
||||
EXPECT_EQ(starting_active, GetGlobalActiveTrackerCount());
|
||||
EXPECT_EQ(starting_inactive + 1, GetGlobalInactiveTrackerCount());
|
||||
|
||||
// Start another thread and ensure it re-uses the existing memory.
|
||||
|
||||
SimpleActivityThread t2("t2", nullptr, Activity::ACT_TASK,
|
||||
ActivityData::ForTask(22));
|
||||
t2.Start();
|
||||
t2.WaitReady();
|
||||
EXPECT_EQ(starting_active + 1, GetGlobalActiveTrackerCount());
|
||||
EXPECT_EQ(starting_inactive, GetGlobalInactiveTrackerCount());
|
||||
|
||||
t2.Exit();
|
||||
t2.Join();
|
||||
EXPECT_EQ(starting_active, GetGlobalActiveTrackerCount());
|
||||
EXPECT_EQ(starting_inactive + 1, GetGlobalInactiveTrackerCount());
|
||||
}
|
||||
|
||||
TEST_F(ActivityTrackerTest, ProcessDeathTest) {
|
||||
// This doesn't actually create and destroy a process. Instead, it uses for-
|
||||
// testing interfaces to simulate data created by other processes.
|
||||
const ProcessId other_process_id = GetCurrentProcId() + 1;
|
||||
|
||||
GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0);
|
||||
GlobalActivityTracker* global = GlobalActivityTracker::Get();
|
||||
ThreadActivityTracker* thread = global->GetOrCreateTrackerForCurrentThread();
|
||||
|
||||
// Get callbacks for process exit.
|
||||
global->SetProcessExitCallback(
|
||||
BindRepeating(&ActivityTrackerTest::HandleProcessExit, Unretained(this)));
|
||||
|
||||
// Pretend than another process has started.
|
||||
global->RecordProcessLaunch(other_process_id, FILE_PATH_LITERAL("foo --bar"));
|
||||
|
||||
// Do some activities.
|
||||
PendingTask task(FROM_HERE, DoNothing());
|
||||
ScopedTaskRunActivity activity(task);
|
||||
ActivityUserData& user_data = activity.user_data();
|
||||
ASSERT_NE(0U, user_data.id());
|
||||
|
||||
// Get the memory-allocator references to that data.
|
||||
PersistentMemoryAllocator::Reference proc_data_ref =
|
||||
global->allocator()->GetAsReference(
|
||||
global->process_data().GetBaseAddress(),
|
||||
GlobalActivityTracker::kTypeIdProcessDataRecord);
|
||||
ASSERT_TRUE(proc_data_ref);
|
||||
PersistentMemoryAllocator::Reference tracker_ref =
|
||||
global->allocator()->GetAsReference(
|
||||
thread->GetBaseAddress(),
|
||||
GlobalActivityTracker::kTypeIdActivityTracker);
|
||||
ASSERT_TRUE(tracker_ref);
|
||||
PersistentMemoryAllocator::Reference user_data_ref =
|
||||
global->allocator()->GetAsReference(
|
||||
user_data.GetBaseAddress(),
|
||||
GlobalActivityTracker::kTypeIdUserDataRecord);
|
||||
ASSERT_TRUE(user_data_ref);
|
||||
|
||||
// Make a copy of the thread-tracker state so it can be restored later.
|
||||
const size_t tracker_size = global->allocator()->GetAllocSize(tracker_ref);
|
||||
std::unique_ptr<char[]> tracker_copy(new char[tracker_size]);
|
||||
memcpy(tracker_copy.get(), thread->GetBaseAddress(), tracker_size);
|
||||
|
||||
// Change the objects to appear to be owned by another process. Use a "past"
|
||||
// time so that exit-time is always later than create-time.
|
||||
const int64_t past_stamp = Time::Now().ToInternalValue() - 1;
|
||||
ProcessId owning_id;
|
||||
int64_t stamp;
|
||||
ASSERT_TRUE(ActivityUserData::GetOwningProcessId(
|
||||
global->process_data().GetBaseAddress(), &owning_id, &stamp));
|
||||
EXPECT_NE(other_process_id, owning_id);
|
||||
ASSERT_TRUE(ThreadActivityTracker::GetOwningProcessId(
|
||||
thread->GetBaseAddress(), &owning_id, &stamp));
|
||||
EXPECT_NE(other_process_id, owning_id);
|
||||
ASSERT_TRUE(ActivityUserData::GetOwningProcessId(user_data.GetBaseAddress(),
|
||||
&owning_id, &stamp));
|
||||
EXPECT_NE(other_process_id, owning_id);
|
||||
global->process_data().SetOwningProcessIdForTesting(other_process_id,
|
||||
past_stamp);
|
||||
thread->SetOwningProcessIdForTesting(other_process_id, past_stamp);
|
||||
user_data.SetOwningProcessIdForTesting(other_process_id, past_stamp);
|
||||
ASSERT_TRUE(ActivityUserData::GetOwningProcessId(
|
||||
global->process_data().GetBaseAddress(), &owning_id, &stamp));
|
||||
EXPECT_EQ(other_process_id, owning_id);
|
||||
ASSERT_TRUE(ThreadActivityTracker::GetOwningProcessId(
|
||||
thread->GetBaseAddress(), &owning_id, &stamp));
|
||||
EXPECT_EQ(other_process_id, owning_id);
|
||||
ASSERT_TRUE(ActivityUserData::GetOwningProcessId(user_data.GetBaseAddress(),
|
||||
&owning_id, &stamp));
|
||||
EXPECT_EQ(other_process_id, owning_id);
|
||||
|
||||
// Check that process exit will perform callback and free the allocations.
|
||||
ASSERT_EQ(ProcessId{0}, exit_id_);
|
||||
ASSERT_EQ(GlobalActivityTracker::kTypeIdProcessDataRecord,
|
||||
global->allocator()->GetType(proc_data_ref));
|
||||
ASSERT_EQ(GlobalActivityTracker::kTypeIdActivityTracker,
|
||||
global->allocator()->GetType(tracker_ref));
|
||||
ASSERT_EQ(GlobalActivityTracker::kTypeIdUserDataRecord,
|
||||
global->allocator()->GetType(user_data_ref));
|
||||
global->RecordProcessExit(other_process_id, 0);
|
||||
EXPECT_EQ(other_process_id, exit_id_);
|
||||
EXPECT_EQ("foo --bar", exit_command_);
|
||||
EXPECT_EQ(GlobalActivityTracker::kTypeIdProcessDataRecordFree,
|
||||
global->allocator()->GetType(proc_data_ref));
|
||||
EXPECT_EQ(GlobalActivityTracker::kTypeIdActivityTrackerFree,
|
||||
global->allocator()->GetType(tracker_ref));
|
||||
EXPECT_EQ(GlobalActivityTracker::kTypeIdUserDataRecordFree,
|
||||
global->allocator()->GetType(user_data_ref));
|
||||
|
||||
// Restore memory contents and types so things don't crash when doing real
|
||||
// process clean-up.
|
||||
memcpy(const_cast<void*>(thread->GetBaseAddress()), tracker_copy.get(),
|
||||
tracker_size);
|
||||
global->allocator()->ChangeType(
|
||||
proc_data_ref, GlobalActivityTracker::kTypeIdProcessDataRecord,
|
||||
GlobalActivityTracker::kTypeIdUserDataRecordFree, false);
|
||||
global->allocator()->ChangeType(
|
||||
tracker_ref, GlobalActivityTracker::kTypeIdActivityTracker,
|
||||
GlobalActivityTracker::kTypeIdActivityTrackerFree, false);
|
||||
global->allocator()->ChangeType(
|
||||
user_data_ref, GlobalActivityTracker::kTypeIdUserDataRecord,
|
||||
GlobalActivityTracker::kTypeIdUserDataRecordFree, false);
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
@ -7,7 +7,6 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "base/compiler_specific.h"
|
||||
#include "base/debug/activity_tracker.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/test/gtest_util.h"
|
||||
#include "base/threading/platform_thread.h"
|
||||
@ -154,48 +153,6 @@ TEST(LockTest, TryLock) {
|
||||
lock.Release();
|
||||
}
|
||||
|
||||
TEST(LockTest, TryTrackedLock) {
|
||||
// Enable the activity tracker.
|
||||
debug::GlobalActivityTracker::CreateWithLocalMemory(64 << 10, 0, "", 3, 0);
|
||||
|
||||
Lock lock;
|
||||
|
||||
ASSERT_TRUE(lock.Try());
|
||||
lock.AssertAcquired();
|
||||
|
||||
// This thread will not be able to get the lock.
|
||||
{
|
||||
TryLockTestThread thread(&lock);
|
||||
PlatformThreadHandle handle;
|
||||
|
||||
ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle));
|
||||
|
||||
PlatformThread::Join(handle);
|
||||
|
||||
ASSERT_FALSE(thread.got_lock());
|
||||
}
|
||||
|
||||
lock.Release();
|
||||
|
||||
// This thread will....
|
||||
{
|
||||
TryLockTestThread thread(&lock);
|
||||
PlatformThreadHandle handle;
|
||||
|
||||
ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle));
|
||||
|
||||
PlatformThread::Join(handle);
|
||||
|
||||
ASSERT_TRUE(thread.got_lock());
|
||||
// But it released it....
|
||||
ASSERT_TRUE(lock.Try());
|
||||
lock.AssertAcquired();
|
||||
}
|
||||
|
||||
lock.Release();
|
||||
debug::GlobalActivityTracker::ReleaseForTesting();
|
||||
}
|
||||
|
||||
// Tests that locks actually exclude -------------------------------------------
|
||||
|
||||
class MutexLockTestThread : public PlatformThread::Delegate {
|
||||
|
@ -54,8 +54,6 @@ chrome/install_static/install_modes.h
|
||||
chrome/install_static/install_util.h
|
||||
chrome/install_static/test/scoped_install_details.h
|
||||
chrome/installer/util/google_update_settings.h
|
||||
components/browser_watcher/features.h
|
||||
components/browser_watcher/stability_paths.h
|
||||
components/cdm/browser/cdm_message_filter_android.h
|
||||
components/device_event_log/device_event_log_export.h
|
||||
components/login/login_export.h
|
||||
|
@ -207,7 +207,6 @@ if (!is_android && !is_mac) {
|
||||
"//chrome/install_static:secondary_module",
|
||||
"//chrome/installer/util:constants",
|
||||
"//chrome/installer/util:did_run_support",
|
||||
"//components/browser_watcher:browser_watcher_client",
|
||||
"//components/crash/core/app",
|
||||
"//components/crash/core/app:run_as_crashpad_handler",
|
||||
"//components/crash/core/common",
|
||||
@ -412,7 +411,6 @@ if (is_win) {
|
||||
"//chrome/common/profiler",
|
||||
"//chrome/install_static:install_static_util",
|
||||
"//chrome/install_static:secondary_module",
|
||||
"//components/browser_watcher:stability_client",
|
||||
"//components/crash/core/app",
|
||||
"//components/policy:generated",
|
||||
"//content/public/app",
|
||||
|
@ -232,7 +232,6 @@ static_library("test_support") {
|
||||
deps += [
|
||||
"//chrome/chrome_elf:test_stubs",
|
||||
"//chrome/install_static:install_static_util",
|
||||
"//components/browser_watcher:stability_client",
|
||||
"//sandbox/win:sandbox",
|
||||
]
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ include_rules = [
|
||||
"+chromeos/hugepage_text/hugepage_text.h",
|
||||
"+chromeos/lacros",
|
||||
"+chromeos/startup",
|
||||
"+components/browser_watcher",
|
||||
"+components/component_updater",
|
||||
"+components/content_settings/core/common/content_settings_pattern.h",
|
||||
"+components/crash",
|
||||
|
@ -110,8 +110,6 @@
|
||||
#include "chrome/chrome_elf/chrome_elf_main.h"
|
||||
#include "chrome/common/child_process_logging.h"
|
||||
#include "chrome/common/win/delay_load_failure_hook.h"
|
||||
#include "chrome/install_static/install_util.h"
|
||||
#include "components/browser_watcher/extended_crash_reporting.h"
|
||||
#include "sandbox/win/src/sandbox.h"
|
||||
#include "sandbox/win/src/sandbox_factory.h"
|
||||
#include "ui/base/resource/resource_bundle_win.h"
|
||||
@ -289,29 +287,6 @@ bool IsSandboxedProcess() {
|
||||
return is_sandboxed_process_func && is_sandboxed_process_func();
|
||||
}
|
||||
|
||||
void SetUpExtendedCrashReporting(bool is_browser_process) {
|
||||
browser_watcher::ExtendedCrashReporting* extended_crash_reporting =
|
||||
browser_watcher::ExtendedCrashReporting::SetUpIfEnabled(
|
||||
is_browser_process
|
||||
? browser_watcher::ExtendedCrashReporting::kBrowserProcess
|
||||
: browser_watcher::ExtendedCrashReporting::kOther);
|
||||
|
||||
if (!extended_crash_reporting)
|
||||
return;
|
||||
|
||||
// Record product, version, channel and special build strings.
|
||||
wchar_t exe_file[MAX_PATH] = {};
|
||||
CHECK(::GetModuleFileName(nullptr, exe_file, std::size(exe_file)));
|
||||
|
||||
std::wstring product_name, version_number, channel_name, special_build;
|
||||
install_static::GetExecutableVersionDetails(
|
||||
exe_file, &product_name, &version_number, &special_build, &channel_name);
|
||||
|
||||
extended_crash_reporting->SetProductStrings(
|
||||
base::WideToUTF16(product_name), base::WideToUTF16(version_number),
|
||||
base::WideToUTF16(channel_name), base::WideToUTF16(special_build));
|
||||
}
|
||||
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
|
||||
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
|
||||
@ -971,7 +946,6 @@ void ChromeMainDelegate::CommonEarlyInitialization() {
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
SetUpExtendedCrashReporting(is_browser_process);
|
||||
base::sequence_manager::internal::ThreadControllerPowerMonitor::
|
||||
InitializeOnMainThread();
|
||||
base::InitializePlatformThreadFeatures();
|
||||
|
@ -5781,8 +5781,6 @@ static_library("browser") {
|
||||
"//chrome/installer/util:with_no_strings",
|
||||
"//chrome/notification_helper:constants",
|
||||
"//chrome/services/util_win/public/mojom",
|
||||
"//components/browser_watcher:browser_watcher_client",
|
||||
"//components/browser_watcher:stability_client",
|
||||
"//components/chrome_cleaner/public/constants",
|
||||
"//components/crash/core/app",
|
||||
"//components/crash/core/app:crash_export_thunk_include",
|
||||
|
@ -73,7 +73,6 @@ include_rules = [
|
||||
"+components/browser_ui/site_settings",
|
||||
"+components/browser_ui/strings",
|
||||
"+components/browser_ui/styles",
|
||||
"+components/browser_watcher",
|
||||
"+components/browsing_data/content",
|
||||
"+components/browsing_data/core",
|
||||
"+components/browsing_topics",
|
||||
|
@ -600,10 +600,7 @@ test("components_unittests") {
|
||||
}
|
||||
|
||||
if (is_win) {
|
||||
deps += [
|
||||
"//components/browser_watcher:unit_tests",
|
||||
"//components/winhttp:unit_tests",
|
||||
]
|
||||
deps += [ "//components/winhttp:unit_tests" ]
|
||||
}
|
||||
|
||||
if (enable_printing) {
|
||||
|
@ -1,128 +0,0 @@
|
||||
# Copyright 2015 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import("//third_party/protobuf/proto_library.gni")
|
||||
|
||||
if (is_win) {
|
||||
proto_library("activity_report_proto") {
|
||||
sources = [ "activity_report.proto" ]
|
||||
}
|
||||
|
||||
group("browser_watcher_client") {
|
||||
deps = [
|
||||
":activity_report_proto",
|
||||
":stability_client",
|
||||
":stability_common",
|
||||
"//base",
|
||||
"//components/metrics",
|
||||
"//third_party/crashpad/crashpad/client",
|
||||
]
|
||||
}
|
||||
|
||||
# This target links into the crashpad handler which lives in chrome_elf.dll
|
||||
# on Windows.
|
||||
static_library("activity_report") {
|
||||
sources = [
|
||||
"activity_report_user_stream_data_source.cc",
|
||||
"activity_report_user_stream_data_source.h",
|
||||
]
|
||||
deps = [
|
||||
":activity_report_proto",
|
||||
":stability_client",
|
||||
":stability_common",
|
||||
"//base",
|
||||
"//third_party/crashpad/crashpad/client",
|
||||
"//third_party/crashpad/crashpad/compat",
|
||||
"//third_party/crashpad/crashpad/handler",
|
||||
"//third_party/crashpad/crashpad/minidump",
|
||||
"//third_party/crashpad/crashpad/snapshot",
|
||||
]
|
||||
}
|
||||
|
||||
static_library("stability_common") {
|
||||
sources = [
|
||||
"activity_report_extractor.cc",
|
||||
"activity_report_extractor.h",
|
||||
"minidump_user_streams.h",
|
||||
]
|
||||
deps = [
|
||||
":activity_report_proto",
|
||||
":stability_client",
|
||||
"//base",
|
||||
"//components/variations",
|
||||
"//third_party/crashpad/crashpad/client:client",
|
||||
"//third_party/crashpad/crashpad/util",
|
||||
]
|
||||
}
|
||||
|
||||
static_library("stability_client") {
|
||||
sources = [
|
||||
"activity_data_names.cc",
|
||||
"activity_data_names.h",
|
||||
"activity_tracker_annotation.cc",
|
||||
"activity_tracker_annotation.h",
|
||||
"extended_crash_reporting.cc",
|
||||
"extended_crash_reporting.h",
|
||||
"extended_crash_reporting_metrics.cc",
|
||||
"extended_crash_reporting_metrics.h",
|
||||
"features.cc",
|
||||
"features.h",
|
||||
]
|
||||
deps = [
|
||||
":activity_report_proto",
|
||||
"//base",
|
||||
"//third_party/crashpad/crashpad/client",
|
||||
"//third_party/crashpad/crashpad/util",
|
||||
]
|
||||
|
||||
# This needs to be a public dep because of base::win::PEImage.
|
||||
public_deps = [ "//base:base_static" ]
|
||||
}
|
||||
|
||||
source_set("unit_tests") {
|
||||
testonly = true
|
||||
sources = [
|
||||
"activity_report_extractor_unittest.cc",
|
||||
"activity_tracker_annotation_unittest.cc",
|
||||
"extended_crash_reporting_win_unittest.cc",
|
||||
]
|
||||
deps = [
|
||||
":activity_report_proto",
|
||||
":browser_watcher_client",
|
||||
":stability_client",
|
||||
":stability_common",
|
||||
"//base",
|
||||
"//base/test:test_support",
|
||||
"//components/crash/core/common:crash_key",
|
||||
"//components/metrics:metrics",
|
||||
"//testing/gmock",
|
||||
"//testing/gtest",
|
||||
"//third_party/crashpad/crashpad/client",
|
||||
|
||||
# TODO(manzagop): remove this lib once Crashpad writes the minidumps.
|
||||
"//third_party/crashpad/crashpad/minidump",
|
||||
"//third_party/crashpad/crashpad/snapshot",
|
||||
"//third_party/crashpad/crashpad/util",
|
||||
]
|
||||
}
|
||||
|
||||
executable("dump_stability") {
|
||||
sources = [ "dump_stability_report_main_win.cc" ]
|
||||
deps = [
|
||||
":activity_report_proto",
|
||||
"//base",
|
||||
]
|
||||
}
|
||||
|
||||
executable("fetch_system_session_events") {
|
||||
sources = [ "fetch_system_session_events_main_win.cc" ]
|
||||
deps = [
|
||||
":activity_report_proto",
|
||||
":stability_client",
|
||||
":stability_common",
|
||||
"//base",
|
||||
"//components/metrics:metrics",
|
||||
]
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
include_rules = [
|
||||
"+components/metrics",
|
||||
"+components/variations",
|
||||
"+components/crash",
|
||||
"+third_party/crashpad/crashpad",
|
||||
]
|
@ -1,3 +0,0 @@
|
||||
monorail {
|
||||
component: "Internals>PlatformIntegration"
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
jessemckenna@google.com
|
||||
joenotcharles@google.com
|
||||
|
||||
chrisha@chromium.org # emeritus
|
@ -1,24 +0,0 @@
|
||||
// Copyright 2020 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "components/browser_watcher/activity_data_names.h"
|
||||
|
||||
namespace browser_watcher {
|
||||
|
||||
const char kActivityChannel[] = "channel";
|
||||
const char kActivityExecutionPhase[] = "execution-phase";
|
||||
const char kActivityKeepAlive[] = "keep-alive";
|
||||
const char kActivityPlatform[] = "platform";
|
||||
const char kActivityProcessType[] = "ptype";
|
||||
const char kActivityProduct[] = "product";
|
||||
const char kActivityReporterChannel[] = "reporter-channel";
|
||||
const char kActivityReporterPlatform[] = "reporter-platform";
|
||||
const char kActivityReporterProduct[] = "reporter-product";
|
||||
const char kActivityReporterVersion[] = "reporter-version";
|
||||
const char kActivityRestartAllowed[] = "restart-allowed";
|
||||
const char kActivitySpecialBuild[] = "special-build";
|
||||
const char kActivityStartTimestamp[] = "start-timestamp";
|
||||
const char kActivityVersion[] = "version";
|
||||
|
||||
} // namespace browser_watcher
|
@ -1,28 +0,0 @@
|
||||
// Copyright 2020 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef COMPONENTS_BROWSER_WATCHER_ACTIVITY_DATA_NAMES_H_
|
||||
#define COMPONENTS_BROWSER_WATCHER_ACTIVITY_DATA_NAMES_H_
|
||||
|
||||
namespace browser_watcher {
|
||||
|
||||
// Alphabetical list of stability data names.
|
||||
extern const char kActivityChannel[];
|
||||
extern const char kActivityExecutionPhase[];
|
||||
extern const char kActivityKeepAlive[];
|
||||
extern const char kActivityPlatform[];
|
||||
extern const char kActivityProcessType[];
|
||||
extern const char kActivityProduct[];
|
||||
extern const char kActivityReporterChannel[];
|
||||
extern const char kActivityReporterPlatform[];
|
||||
extern const char kActivityReporterProduct[];
|
||||
extern const char kActivityReporterVersion[];
|
||||
extern const char kActivityRestartAllowed[];
|
||||
extern const char kActivitySpecialBuild[];
|
||||
extern const char kActivityStartTimestamp[];
|
||||
extern const char kActivityVersion[];
|
||||
|
||||
} // namespace browser_watcher
|
||||
|
||||
#endif // COMPONENTS_BROWSER_WATCHER_ACTIVITY_DATA_NAMES_H_
|
@ -1,275 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
package browser_watcher;
|
||||
|
||||
// The state of the system on which Chrome is running (shutting down, battery
|
||||
// level, load, etc.).
|
||||
// Next id: 2
|
||||
message SystemState {
|
||||
// The state of a system session. A system session begins when the system
|
||||
// starts and ends when it shuts down.
|
||||
enum SessionState {
|
||||
UNKNOWN = 0;
|
||||
CLEAN = 1; // Normal shutdown.
|
||||
UNCLEAN = 2; // Abnormal shutdown (system crash, power loss).
|
||||
}
|
||||
|
||||
// The state of the system session that contained Chrome's execution.
|
||||
optional SessionState session_state = 1;
|
||||
}
|
||||
|
||||
// Next id: 10
|
||||
message CodeModule {
|
||||
// The base address of this code module as it was loaded by the process.
|
||||
optional int64 base_address = 1;
|
||||
|
||||
// The size of the code module.
|
||||
optional int64 size = 2;
|
||||
|
||||
// The path or file name that the code module was loaded from.
|
||||
optional string code_file = 3;
|
||||
|
||||
// An identifying string used to discriminate between multiple versions and
|
||||
// builds of the same code module. This may contain a uuid, timestamp,
|
||||
// version number, or any combination of this or other information, in an
|
||||
// implementation-defined format.
|
||||
optional string code_identifier = 4;
|
||||
|
||||
// The filename containing debugging information associated with the code
|
||||
// module. If debugging information is stored in a file separate from the
|
||||
// code module itself (as is the case when .pdb or .dSYM files are used),
|
||||
// this will be different from code_file. If debugging information is
|
||||
// stored in the code module itself (possibly prior to stripping), this
|
||||
// will be the same as code_file.
|
||||
optional string debug_file = 5;
|
||||
|
||||
// An identifying string similar to code_identifier, but identifies a
|
||||
// specific version and build of the associated debug file. This may be
|
||||
// the same as code_identifier when the debug_file and code_file are
|
||||
// identical or when the same identifier is used to identify distinct
|
||||
// debug and code files.
|
||||
optional string debug_identifier = 6;
|
||||
|
||||
// A human-readable representation of the code module's version.
|
||||
optional string version = 7;
|
||||
|
||||
optional int64 shrink_down_delta = 8;
|
||||
|
||||
// Whether the module was still loaded into memory.
|
||||
optional bool is_unloaded = 9;
|
||||
}
|
||||
|
||||
// A typed value holds values of interest or references to such values.
|
||||
// Next id: 9
|
||||
message TypedValue {
|
||||
// A reference to a value of interest.
|
||||
message Reference {
|
||||
optional uint64 address = 1;
|
||||
optional int64 size = 2;
|
||||
}
|
||||
|
||||
oneof value {
|
||||
bytes bytes_value = 1;
|
||||
Reference bytes_reference = 2;
|
||||
string string_value = 3;
|
||||
Reference string_reference = 4;
|
||||
string char_value = 5;
|
||||
bool bool_value = 6;
|
||||
int64 signed_value = 7;
|
||||
uint64 unsigned_value = 8;
|
||||
}
|
||||
}
|
||||
|
||||
// An activity represents information about something of interest on a thread.
|
||||
// Next id: 18
|
||||
message Activity {
|
||||
enum Type {
|
||||
UNKNOWN = 0;
|
||||
ACT_TASK_RUN = 1;
|
||||
ACT_LOCK_ACQUIRE = 2;
|
||||
ACT_EVENT_WAIT = 3;
|
||||
ACT_THREAD_JOIN = 4;
|
||||
ACT_PROCESS_WAIT = 5;
|
||||
ACT_GENERIC = 6;
|
||||
}
|
||||
|
||||
// Identifies the type of the activity (and specifies which fields are
|
||||
// relevant).
|
||||
optional Type type = 1;
|
||||
|
||||
// Creation time of the activity.
|
||||
optional int64 time = 2;
|
||||
|
||||
// The address that pushed the activity onto the stack.
|
||||
optional uint64 address = 11;
|
||||
|
||||
// The address that is the origin of the activity. This is useful for things
|
||||
// like tasks that are posted from a completely different thread though most
|
||||
// activities will leave it null.
|
||||
optional uint64 origin_address = 3;
|
||||
|
||||
// The sequence identifier of the posted task.
|
||||
// Expected for ACT_TASK_*
|
||||
optional uint64 task_sequence_id = 4;
|
||||
|
||||
// The memory address of the lock object.
|
||||
optional uint64 lock_address = 5;
|
||||
|
||||
// The memory address of the event object.
|
||||
optional uint64 event_address = 6;
|
||||
|
||||
// A unique identifier for a thread within a process.
|
||||
optional int64 thread_id = 7;
|
||||
|
||||
// A unique identifier for a process.
|
||||
optional int64 process_id = 8;
|
||||
|
||||
// An arbitrary identifier used for association.
|
||||
optional uint32 generic_id = 12;
|
||||
|
||||
// An arbitrary value used for information purposes.
|
||||
optional int32 generic_data = 13;
|
||||
|
||||
// Tag ids 10 and 14-17 are reserved for server side augmentation.
|
||||
|
||||
// A key-value store.
|
||||
map<string, TypedValue> user_data = 9;
|
||||
}
|
||||
|
||||
// Details about an exception.
|
||||
// Next id: 5
|
||||
message Exception {
|
||||
optional uint32 code = 1;
|
||||
optional uint64 program_counter = 2;
|
||||
optional uint64 exception_address = 3;
|
||||
optional int64 time = 4;
|
||||
}
|
||||
|
||||
// The state of a thread.
|
||||
// Next id: 6
|
||||
message ThreadState {
|
||||
// The name of the thread, up to a maxiumum length.
|
||||
optional string thread_name = 1;
|
||||
|
||||
// The identifier of the thread.
|
||||
optional int64 thread_id = 2;
|
||||
|
||||
// The activity stack. |activity_count| specifies the number of activities on
|
||||
// stack and |activities| holds the base of the stack (up to a maximum size).
|
||||
optional int32 activity_count = 3;
|
||||
repeated Activity activities = 4;
|
||||
|
||||
// The last exception to be successfully captured. Note this exception may
|
||||
// have been recovered from.
|
||||
optional Exception exception = 5;
|
||||
}
|
||||
|
||||
// The state of a process.
|
||||
// Next id: 7
|
||||
message ProcessState {
|
||||
enum Type {
|
||||
UNKNOWN_PROCESS = 0;
|
||||
BROWSER_PROCESS = 1;
|
||||
WATCHER_PROCESS = 2;
|
||||
}
|
||||
|
||||
message MemoryState {
|
||||
message WindowsMemory {
|
||||
// The private byte usage of the process. Unit is 4K pages.
|
||||
optional uint32 process_private_usage = 1;
|
||||
// The peak working set usage of the process. Unit is 4K pages.
|
||||
optional uint32 process_peak_workingset_size = 2;
|
||||
// The peak pagefile usage of the process. Unit is 4K pages.
|
||||
optional uint32 process_peak_pagefile_usage = 3;
|
||||
// The allocation request that caused OOM, bytes.
|
||||
optional uint32 process_allocation_attempt = 4;
|
||||
// The number of opened handles in the process.
|
||||
optional uint32 process_handle_count = 5;
|
||||
}
|
||||
|
||||
optional WindowsMemory windows_memory = 1;
|
||||
}
|
||||
|
||||
// The identifier of the process.
|
||||
optional int64 process_id = 3;
|
||||
optional Type process_type = 5;
|
||||
|
||||
// Note: likely only a subset of modules of interest (e.g. Chromium's own
|
||||
// modules).
|
||||
repeated CodeModule modules = 1;
|
||||
repeated ThreadState threads = 2;
|
||||
|
||||
optional MemoryState memory_state = 4;
|
||||
|
||||
// A key-value store global to the process.
|
||||
map<string, TypedValue> data = 6;
|
||||
}
|
||||
|
||||
// Description of a field trial or experiment that the user is currently
|
||||
// enrolled in. This message is an analogue of the UMA proto in
|
||||
// third_party/metrics_proto/system_profile.proto. For details about generating
|
||||
// the identifiers from the field trial and group names, see
|
||||
// variations::MakeActiveGroupId().
|
||||
// Next id: 3
|
||||
message FieldTrial {
|
||||
// A 32-bit identifier for the name of the field trial.
|
||||
optional fixed32 name_id = 1;
|
||||
|
||||
// A 32-bit identifier for the user's group within the field trial.
|
||||
optional fixed32 group_id = 2;
|
||||
}
|
||||
|
||||
// Records the state of system memory at the time of crash.
|
||||
// Next id: 2
|
||||
message SystemMemoryState {
|
||||
message WindowsMemory {
|
||||
// The system commit limit. Unit is number of 4K pages.
|
||||
optional uint32 system_commit_limit = 1;
|
||||
// The amount of system commit remaining. Unit is number of 4K pages.
|
||||
optional uint32 system_commit_remaining = 2;
|
||||
// The current number of open handles.
|
||||
optional uint32 system_handle_count = 3;
|
||||
}
|
||||
|
||||
optional WindowsMemory windows_memory = 1;
|
||||
}
|
||||
|
||||
// A stability report contains information pertaining to the execution of a
|
||||
// single logical instance of a "chrome browser". It is comprised of information
|
||||
// about the system state and about the chrome browser's processes.
|
||||
// Next id: 9
|
||||
// This message would more appropriately be named ActivityReport, but since this
|
||||
// proto is used in the crash backend, renaming it will take some dancing
|
||||
// around.
|
||||
message StabilityReport {
|
||||
// Whether the report is complete. Reports can be incomplete when the
|
||||
// recording size quota is hit.
|
||||
optional bool is_complete = 6;
|
||||
|
||||
// The process identifier of the crashed process.
|
||||
optional int64 crashed_process_id = 8;
|
||||
|
||||
// State pertaining to the system.
|
||||
optional SystemState system_state = 1;
|
||||
|
||||
// State pertaining to Chrome's processes.
|
||||
repeated ProcessState process_states = 2;
|
||||
|
||||
// Log messages. This is empty on official builds.
|
||||
// TODO(manzagop): attribute messages to their process.
|
||||
repeated string log_messages = 3;
|
||||
|
||||
// A global key-value store.
|
||||
map<string, TypedValue> global_data = 4;
|
||||
|
||||
// The field trials the user is currently enrolled in.
|
||||
repeated FieldTrial field_trials = 5;
|
||||
|
||||
optional SystemMemoryState system_memory_state = 7;
|
||||
}
|
@ -1,307 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "components/browser_watcher/activity_report_extractor.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/check_op.h"
|
||||
#include "base/debug/activity_analyzer.h"
|
||||
#include "base/notreached.h"
|
||||
#include "base/strings/string_piece.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "components/browser_watcher/activity_data_names.h"
|
||||
#include "third_party/crashpad/crashpad/util/misc/uuid.h"
|
||||
|
||||
namespace browser_watcher {
|
||||
|
||||
using ActivitySnapshot = base::debug::ThreadActivityAnalyzer::Snapshot;
|
||||
using base::PersistentMemoryAllocator;
|
||||
using base::debug::ActivityUserData;
|
||||
using base::debug::GlobalActivityAnalyzer;
|
||||
using base::debug::GlobalActivityTracker;
|
||||
using base::debug::ThreadActivityAnalyzer;
|
||||
|
||||
namespace {
|
||||
|
||||
// Collects stability user data from the recorded format to the collected
|
||||
// format.
|
||||
void CollectUserData(
|
||||
const ActivityUserData::Snapshot& recorded_map,
|
||||
google::protobuf::Map<std::string, TypedValue>* collected_map,
|
||||
StabilityReport* report) {
|
||||
DCHECK(collected_map);
|
||||
|
||||
for (const auto& name_and_value : recorded_map) {
|
||||
const std::string& key = name_and_value.first;
|
||||
const ActivityUserData::TypedValue& recorded_value = name_and_value.second;
|
||||
TypedValue collected_value;
|
||||
|
||||
switch (recorded_value.type()) {
|
||||
case ActivityUserData::END_OF_VALUES:
|
||||
NOTREACHED();
|
||||
break;
|
||||
case ActivityUserData::RAW_VALUE: {
|
||||
base::StringPiece raw = recorded_value.Get();
|
||||
collected_value.set_bytes_value(raw.data(), raw.size());
|
||||
break;
|
||||
}
|
||||
case ActivityUserData::RAW_VALUE_REFERENCE: {
|
||||
base::StringPiece recorded_ref = recorded_value.GetReference();
|
||||
TypedValue::Reference* collected_ref =
|
||||
collected_value.mutable_bytes_reference();
|
||||
collected_ref->set_address(
|
||||
reinterpret_cast<uintptr_t>(recorded_ref.data()));
|
||||
collected_ref->set_size(recorded_ref.size());
|
||||
break;
|
||||
}
|
||||
case ActivityUserData::STRING_VALUE: {
|
||||
base::StringPiece value = recorded_value.GetString();
|
||||
collected_value.set_string_value(value.data(), value.size());
|
||||
|
||||
// Promote version information to the global key value store.
|
||||
if (report) {
|
||||
bool should_promote =
|
||||
key == kActivityProduct || key == kActivityChannel ||
|
||||
key == kActivityPlatform || key == kActivityVersion;
|
||||
if (should_promote) {
|
||||
// TODO(siggi): Copy the promoted data or perhaps remove this
|
||||
// promotion once the backend is able to display the per-process
|
||||
// data.
|
||||
(*report->mutable_global_data())[key].Swap(&collected_value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ActivityUserData::STRING_VALUE_REFERENCE: {
|
||||
base::StringPiece recorded_ref = recorded_value.GetStringReference();
|
||||
TypedValue::Reference* collected_ref =
|
||||
collected_value.mutable_string_reference();
|
||||
collected_ref->set_address(
|
||||
reinterpret_cast<uintptr_t>(recorded_ref.data()));
|
||||
collected_ref->set_size(recorded_ref.size());
|
||||
break;
|
||||
}
|
||||
case ActivityUserData::CHAR_VALUE: {
|
||||
char char_value = recorded_value.GetChar();
|
||||
collected_value.set_char_value(&char_value, 1);
|
||||
break;
|
||||
}
|
||||
case ActivityUserData::BOOL_VALUE:
|
||||
collected_value.set_bool_value(recorded_value.GetBool());
|
||||
break;
|
||||
case ActivityUserData::SIGNED_VALUE:
|
||||
collected_value.set_signed_value(recorded_value.GetInt());
|
||||
|
||||
// Promote the execution timestamp to the global key value store.
|
||||
if (report && key == kActivityStartTimestamp) {
|
||||
(*report->mutable_global_data())[key].Swap(&collected_value);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
case ActivityUserData::UNSIGNED_VALUE:
|
||||
collected_value.set_unsigned_value(recorded_value.GetUint());
|
||||
break;
|
||||
}
|
||||
|
||||
(*collected_map)[key].Swap(&collected_value);
|
||||
}
|
||||
}
|
||||
|
||||
void CollectModuleInformation(
|
||||
const std::vector<GlobalActivityTracker::ModuleInfo>& modules,
|
||||
ProcessState* process_state) {
|
||||
DCHECK(process_state);
|
||||
|
||||
for (const GlobalActivityTracker::ModuleInfo& recorded : modules) {
|
||||
CodeModule* collected = process_state->add_modules();
|
||||
collected->set_base_address(recorded.address);
|
||||
collected->set_size(recorded.size);
|
||||
collected->set_code_file(recorded.file);
|
||||
|
||||
// Compute the code identifier using the required format.
|
||||
std::string code_identifier =
|
||||
base::StringPrintf("%08X%zx", recorded.timestamp, recorded.size);
|
||||
collected->set_code_identifier(code_identifier);
|
||||
collected->set_debug_file(recorded.debug_file);
|
||||
|
||||
// Compute the debug identifier using the required format.
|
||||
const crashpad::UUID* uuid =
|
||||
reinterpret_cast<const crashpad::UUID*>(recorded.identifier);
|
||||
std::string debug_identifier = base::StringPrintf(
|
||||
"%08X%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X%x", uuid->data_1,
|
||||
uuid->data_2, uuid->data_3, uuid->data_4[0], uuid->data_4[1],
|
||||
uuid->data_5[0], uuid->data_5[1], uuid->data_5[2], uuid->data_5[3],
|
||||
uuid->data_5[4], uuid->data_5[5], recorded.age);
|
||||
collected->set_debug_identifier(debug_identifier);
|
||||
collected->set_is_unloaded(!recorded.is_loaded);
|
||||
}
|
||||
}
|
||||
|
||||
void CollectActivity(const base::debug::Activity& recorded,
|
||||
Activity* collected) {
|
||||
DCHECK(collected);
|
||||
|
||||
collected->set_time(recorded.time_internal);
|
||||
collected->set_address(recorded.calling_address);
|
||||
collected->set_origin_address(recorded.origin_address);
|
||||
|
||||
// TODO(manzagop): the current collection deals with specific activity types;
|
||||
// consider modifying it to handle the notion of activity categories.
|
||||
switch (recorded.activity_type) {
|
||||
case base::debug::Activity::ACT_TASK_RUN:
|
||||
collected->set_type(Activity::ACT_TASK_RUN);
|
||||
collected->set_task_sequence_id(recorded.data.task.sequence_id);
|
||||
break;
|
||||
case base::debug::Activity::ACT_LOCK_ACQUIRE:
|
||||
collected->set_type(Activity::ACT_LOCK_ACQUIRE);
|
||||
collected->set_lock_address(recorded.data.lock.lock_address);
|
||||
break;
|
||||
case base::debug::Activity::ACT_EVENT_WAIT:
|
||||
collected->set_type(Activity::ACT_EVENT_WAIT);
|
||||
collected->set_event_address(recorded.data.event.event_address);
|
||||
break;
|
||||
case base::debug::Activity::ACT_THREAD_JOIN:
|
||||
collected->set_type(Activity::ACT_THREAD_JOIN);
|
||||
collected->set_thread_id(recorded.data.thread.thread_id);
|
||||
break;
|
||||
case base::debug::Activity::ACT_PROCESS_WAIT:
|
||||
collected->set_type(Activity::ACT_PROCESS_WAIT);
|
||||
collected->set_process_id(recorded.data.process.process_id);
|
||||
break;
|
||||
case base::debug::Activity::ACT_GENERIC:
|
||||
collected->set_type(Activity::ACT_GENERIC);
|
||||
collected->set_generic_id(recorded.data.generic.id);
|
||||
collected->set_generic_data(recorded.data.generic.info);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CollectException(const base::debug::Activity& recorded,
|
||||
Exception* collected) {
|
||||
DCHECK(collected);
|
||||
collected->set_code(recorded.data.exception.code);
|
||||
collected->set_program_counter(recorded.calling_address);
|
||||
collected->set_exception_address(recorded.origin_address);
|
||||
collected->set_time(recorded.time_internal);
|
||||
}
|
||||
|
||||
void CollectThread(
|
||||
const base::debug::ThreadActivityAnalyzer::Snapshot& snapshot,
|
||||
ThreadState* thread_state) {
|
||||
DCHECK(thread_state);
|
||||
|
||||
thread_state->set_thread_name(snapshot.thread_name);
|
||||
thread_state->set_thread_id(snapshot.thread_id);
|
||||
thread_state->set_activity_count(snapshot.activity_stack_depth);
|
||||
|
||||
if (snapshot.last_exception.activity_type ==
|
||||
base::debug::Activity::ACT_EXCEPTION) {
|
||||
CollectException(snapshot.last_exception,
|
||||
thread_state->mutable_exception());
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < snapshot.activity_stack.size(); ++i) {
|
||||
Activity* collected = thread_state->add_activities();
|
||||
|
||||
CollectActivity(snapshot.activity_stack[i], collected);
|
||||
if (i < snapshot.user_data_stack.size()) {
|
||||
CollectUserData(snapshot.user_data_stack[i],
|
||||
collected->mutable_user_data(), nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SetProcessType(ProcessState* process_state) {
|
||||
DCHECK(process_state);
|
||||
google::protobuf::Map<std::string, TypedValue>* process_data =
|
||||
process_state->mutable_data();
|
||||
|
||||
const auto it = process_data->find(kActivityProcessType);
|
||||
if (it == process_data->end())
|
||||
return false;
|
||||
|
||||
const TypedValue& value = it->second;
|
||||
if (value.value_case() != TypedValue::kSignedValue)
|
||||
return false;
|
||||
|
||||
process_state->set_process_type(
|
||||
static_cast<browser_watcher::ProcessState_Type>(value.signed_value()));
|
||||
process_data->erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CollectProcess(int64_t pid,
|
||||
GlobalActivityAnalyzer* analyzer,
|
||||
StabilityReport* report) {
|
||||
DCHECK(analyzer);
|
||||
DCHECK(report);
|
||||
|
||||
ProcessState* process_state = report->add_process_states();
|
||||
|
||||
// Collect thread activity data.
|
||||
ThreadActivityAnalyzer* thread_analyzer = analyzer->GetFirstAnalyzer(pid);
|
||||
for (; thread_analyzer != nullptr;
|
||||
thread_analyzer = analyzer->GetNextAnalyzer()) {
|
||||
// Only valid analyzers are expected per contract of GetFirstAnalyzer /
|
||||
// GetNextAnalyzer.
|
||||
DCHECK(thread_analyzer->IsValid());
|
||||
|
||||
if (!process_state->has_process_id()) {
|
||||
process_state->set_process_id(
|
||||
thread_analyzer->activity_snapshot().process_id);
|
||||
}
|
||||
DCHECK_EQ(thread_analyzer->activity_snapshot().process_id,
|
||||
process_state->process_id());
|
||||
|
||||
ThreadState* thread_state = process_state->add_threads();
|
||||
CollectThread(thread_analyzer->activity_snapshot(), thread_state);
|
||||
}
|
||||
|
||||
// Collect global user data.
|
||||
ActivityUserData::Snapshot process_data_snapshot =
|
||||
analyzer->GetProcessDataSnapshot(pid);
|
||||
CollectUserData(process_data_snapshot, process_state->mutable_data(), report);
|
||||
SetProcessType(process_state);
|
||||
|
||||
// Collect module information.
|
||||
CollectModuleInformation(analyzer->GetModules(pid), process_state);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CollectionStatus Extract(
|
||||
std::unique_ptr<GlobalActivityAnalyzer> global_analyzer,
|
||||
StabilityReport* report) {
|
||||
DCHECK(global_analyzer);
|
||||
DCHECK(report);
|
||||
|
||||
report->set_is_complete(global_analyzer->IsDataComplete());
|
||||
|
||||
// Collect process data.
|
||||
for (int64_t pid = global_analyzer->GetFirstProcess(); pid != 0;
|
||||
pid = global_analyzer->GetNextProcess()) {
|
||||
CollectProcess(pid, global_analyzer.get(), report);
|
||||
}
|
||||
|
||||
// Collect log messages.
|
||||
std::vector<std::string> log_messages = global_analyzer->GetLogMessages();
|
||||
for (const std::string& message : log_messages) {
|
||||
report->add_log_messages(message);
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace browser_watcher
|
@ -1,39 +0,0 @@
|
||||
// Copyright 2017 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
//
|
||||
// Implementation of the collection of a stability file to a protocol buffer.
|
||||
|
||||
#ifndef COMPONENTS_BROWSER_WATCHER_ACTIVITY_REPORT_EXTRACTOR_H_
|
||||
#define COMPONENTS_BROWSER_WATCHER_ACTIVITY_REPORT_EXTRACTOR_H_
|
||||
|
||||
#include "base/debug/activity_analyzer.h"
|
||||
#include "components/browser_watcher/activity_report.pb.h"
|
||||
|
||||
namespace browser_watcher {
|
||||
|
||||
// DO NOT REMOVE OR REORDER VALUES. This is logged persistently in a histogram.
|
||||
enum CollectionStatus {
|
||||
NONE = 0,
|
||||
SUCCESS = 1, // Successfully registered a report with Crashpad.
|
||||
ANALYZER_CREATION_FAILED = 2,
|
||||
DEBUG_FILE_NO_DATA = 3,
|
||||
PREPARE_NEW_CRASH_REPORT_FAILED = 4,
|
||||
WRITE_TO_MINIDUMP_FAILED = 5,
|
||||
DEBUG_FILE_DELETION_FAILED = 6,
|
||||
FINISHED_WRITING_CRASH_REPORT_FAILED = 7,
|
||||
UNCLEAN_SHUTDOWN = 8,
|
||||
UNCLEAN_SESSION = 9,
|
||||
COLLECTION_ATTEMPT = 10,
|
||||
// New values go here.
|
||||
COLLECTION_STATUS_MAX = 11
|
||||
};
|
||||
|
||||
// Extracts a stability report from an existing GlobalActivityAnalyzer.
|
||||
CollectionStatus Extract(
|
||||
std::unique_ptr<base::debug::GlobalActivityAnalyzer> global_analyzer,
|
||||
StabilityReport* report);
|
||||
|
||||
} // namespace browser_watcher
|
||||
|
||||
#endif // COMPONENTS_BROWSER_WATCHER_ACTIVITY_REPORT_EXTRACTOR_H_
|
@ -1,446 +0,0 @@
|
||||
// Copyright 2017 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "components/browser_watcher/activity_report_extractor.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "base/containers/contains.h"
|
||||
#include "base/debug/activity_analyzer.h"
|
||||
#include "base/debug/activity_tracker.h"
|
||||
#include "base/files/file.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/memory_mapped_file.h"
|
||||
#include "base/files/scoped_temp_dir.h"
|
||||
#include "base/metrics/persistent_memory_allocator.h"
|
||||
#include "base/threading/platform_thread.h"
|
||||
#include "base/time/time.h"
|
||||
#include "build/build_config.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "third_party/crashpad/crashpad/client/crash_report_database.h"
|
||||
|
||||
namespace browser_watcher {
|
||||
|
||||
using base::File;
|
||||
using base::FilePath;
|
||||
using base::FilePersistentMemoryAllocator;
|
||||
using base::MemoryMappedFile;
|
||||
using base::PersistentMemoryAllocator;
|
||||
using base::debug::ActivityData;
|
||||
using base::debug::ActivityTrackerMemoryAllocator;
|
||||
using base::debug::ActivityUserData;
|
||||
using base::debug::GlobalActivityAnalyzer;
|
||||
using base::debug::GlobalActivityTracker;
|
||||
using base::debug::ThreadActivityTracker;
|
||||
|
||||
namespace {
|
||||
|
||||
// The tracker creates some data entries internally.
|
||||
const size_t kInternalProcessDatums = 1;
|
||||
|
||||
// Parameters for the activity tracking.
|
||||
const size_t kFileSize = 64 << 10; // 64 KiB
|
||||
const int kStackDepth = 6;
|
||||
const uint64_t kAllocatorId = 0;
|
||||
const char kAllocatorName[] = "PostmortemReportCollectorCollectionTest";
|
||||
const uint64_t kTaskSequenceNum = 42;
|
||||
const uintptr_t kTaskOrigin = 1000U;
|
||||
const uintptr_t kLockAddress = 1001U;
|
||||
const uintptr_t kEventAddress = 1002U;
|
||||
const int kThreadId = 43;
|
||||
const int kProcessId = 44;
|
||||
const int kAnotherThreadId = 45;
|
||||
const uint32_t kGenericId = 46U;
|
||||
const int32_t kGenericData = 47;
|
||||
|
||||
} // namespace
|
||||
|
||||
// Sets up a file backed thread tracker for direct access. A
|
||||
// GlobalActivityTracker is not created, meaning there is no risk of
|
||||
// the instrumentation interfering with the file's content.
|
||||
class StabilityReportExtractorThreadTrackerTest : public testing::Test {
|
||||
public:
|
||||
// Create a proper debug file.
|
||||
void SetUp() override {
|
||||
testing::Test::SetUp();
|
||||
|
||||
// Create a file backed allocator.
|
||||
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
|
||||
debug_file_path_ = temp_dir_.GetPath().AppendASCII("debug_file.pma");
|
||||
allocator_ = CreateAllocator();
|
||||
ASSERT_NE(nullptr, allocator_);
|
||||
|
||||
size_t tracker_mem_size =
|
||||
ThreadActivityTracker::SizeForStackDepth(kStackDepth);
|
||||
ASSERT_GT(kFileSize, tracker_mem_size);
|
||||
|
||||
// Create a tracker.
|
||||
tracker_ = CreateTracker(allocator_.get(), tracker_mem_size);
|
||||
ASSERT_NE(nullptr, tracker_);
|
||||
ASSERT_TRUE(tracker_->IsValid());
|
||||
}
|
||||
|
||||
std::unique_ptr<PersistentMemoryAllocator> CreateAllocator() {
|
||||
// Create the memory mapped file.
|
||||
std::unique_ptr<MemoryMappedFile> mmfile(new MemoryMappedFile());
|
||||
bool success = mmfile->Initialize(
|
||||
File(debug_file_path_, File::FLAG_CREATE | File::FLAG_READ |
|
||||
File::FLAG_WRITE |
|
||||
File::FLAG_WIN_SHARE_DELETE),
|
||||
{0, static_cast<int64_t>(kFileSize)},
|
||||
MemoryMappedFile::READ_WRITE_EXTEND);
|
||||
if (!success || !mmfile->IsValid())
|
||||
return nullptr;
|
||||
|
||||
// Create a persistent memory allocator.
|
||||
if (!FilePersistentMemoryAllocator::IsFileAcceptable(*mmfile, true))
|
||||
return nullptr;
|
||||
return std::make_unique<FilePersistentMemoryAllocator>(
|
||||
std::move(mmfile), kFileSize, kAllocatorId, kAllocatorName, false);
|
||||
}
|
||||
|
||||
std::unique_ptr<ThreadActivityTracker> CreateTracker(
|
||||
PersistentMemoryAllocator* allocator,
|
||||
size_t tracker_mem_size) {
|
||||
// Allocate a block of memory for the tracker to use.
|
||||
PersistentMemoryAllocator::Reference mem_reference = allocator->Allocate(
|
||||
tracker_mem_size, GlobalActivityTracker::kTypeIdActivityTracker);
|
||||
if (mem_reference == 0U)
|
||||
return nullptr;
|
||||
|
||||
// Get the memory's base address.
|
||||
void* mem_base = allocator->GetAsArray<char>(
|
||||
mem_reference, GlobalActivityTracker::kTypeIdActivityTracker,
|
||||
PersistentMemoryAllocator::kSizeAny);
|
||||
if (mem_base == nullptr)
|
||||
return nullptr;
|
||||
|
||||
// Make the allocation iterable so it can be found by other processes.
|
||||
allocator->MakeIterable(mem_reference);
|
||||
|
||||
return std::make_unique<ThreadActivityTracker>(mem_base, tracker_mem_size);
|
||||
}
|
||||
|
||||
void PerformBasicReportValidation(const StabilityReport& report) {
|
||||
// One report with one thread that has the expected name and id.
|
||||
ASSERT_EQ(1, report.process_states_size());
|
||||
const ProcessState& process_state = report.process_states(0);
|
||||
EXPECT_EQ(base::GetCurrentProcId(), process_state.process_id());
|
||||
ASSERT_EQ(1, process_state.threads_size());
|
||||
const ThreadState& thread_state = process_state.threads(0);
|
||||
EXPECT_EQ(base::PlatformThread::GetName(), thread_state.thread_name());
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
EXPECT_EQ(base::PlatformThread::CurrentId(), thread_state.thread_id());
|
||||
#elif BUILDFLAG(IS_POSIX)
|
||||
EXPECT_EQ(base::PlatformThread::CurrentHandle().platform_handle(),
|
||||
thread_state.thread_id());
|
||||
#endif
|
||||
}
|
||||
|
||||
std::unique_ptr<GlobalActivityAnalyzer> CreateAnalyzer() {
|
||||
return GlobalActivityAnalyzer::CreateWithFile(debug_file_path());
|
||||
}
|
||||
|
||||
private:
|
||||
const FilePath& debug_file_path() const { return debug_file_path_; }
|
||||
|
||||
protected:
|
||||
base::ScopedTempDir temp_dir_;
|
||||
FilePath debug_file_path_;
|
||||
|
||||
std::unique_ptr<PersistentMemoryAllocator> allocator_;
|
||||
std::unique_ptr<ThreadActivityTracker> tracker_;
|
||||
};
|
||||
|
||||
TEST_F(StabilityReportExtractorThreadTrackerTest, CollectSuccess) {
|
||||
// Create some activity data.
|
||||
tracker_->PushActivity(reinterpret_cast<void*>(kTaskOrigin),
|
||||
base::debug::Activity::ACT_TASK_RUN,
|
||||
ActivityData::ForTask(kTaskSequenceNum));
|
||||
tracker_->PushActivity(
|
||||
nullptr, base::debug::Activity::ACT_LOCK_ACQUIRE,
|
||||
ActivityData::ForLock(reinterpret_cast<void*>(kLockAddress)));
|
||||
ThreadActivityTracker::ActivityId activity_id = tracker_->PushActivity(
|
||||
nullptr, base::debug::Activity::ACT_EVENT_WAIT,
|
||||
ActivityData::ForEvent(reinterpret_cast<void*>(kEventAddress)));
|
||||
tracker_->PushActivity(nullptr, base::debug::Activity::ACT_THREAD_JOIN,
|
||||
ActivityData::ForThread(kThreadId));
|
||||
tracker_->PushActivity(nullptr, base::debug::Activity::ACT_PROCESS_WAIT,
|
||||
ActivityData::ForProcess(kProcessId));
|
||||
tracker_->PushActivity(nullptr, base::debug::Activity::ACT_GENERIC,
|
||||
ActivityData::ForGeneric(kGenericId, kGenericData));
|
||||
// Note: this exceeds the activity stack's capacity.
|
||||
tracker_->PushActivity(nullptr, base::debug::Activity::ACT_THREAD_JOIN,
|
||||
ActivityData::ForThread(kAnotherThreadId));
|
||||
|
||||
// Add some user data.
|
||||
ActivityTrackerMemoryAllocator user_data_allocator(
|
||||
allocator_.get(), GlobalActivityTracker::kTypeIdUserDataRecord,
|
||||
GlobalActivityTracker::kTypeIdUserDataRecordFree, 1024U, 10U, false);
|
||||
std::unique_ptr<ActivityUserData> user_data =
|
||||
tracker_->GetUserData(activity_id, &user_data_allocator);
|
||||
user_data->SetInt("some_int", 42);
|
||||
|
||||
// Validate collection returns the expected report.
|
||||
StabilityReport report;
|
||||
ASSERT_EQ(SUCCESS, Extract(CreateAnalyzer(), &report));
|
||||
|
||||
// Validate the report.
|
||||
ASSERT_NO_FATAL_FAILURE(PerformBasicReportValidation(report));
|
||||
const ThreadState& thread_state = report.process_states(0).threads(0);
|
||||
|
||||
EXPECT_EQ(7, thread_state.activity_count());
|
||||
ASSERT_EQ(6, thread_state.activities_size());
|
||||
{
|
||||
const Activity& activity = thread_state.activities(0);
|
||||
EXPECT_EQ(Activity::ACT_TASK_RUN, activity.type());
|
||||
EXPECT_EQ(kTaskOrigin, activity.origin_address());
|
||||
EXPECT_EQ(kTaskSequenceNum, activity.task_sequence_id());
|
||||
EXPECT_EQ(0U, activity.user_data().size());
|
||||
}
|
||||
{
|
||||
const Activity& activity = thread_state.activities(1);
|
||||
EXPECT_EQ(Activity::ACT_LOCK_ACQUIRE, activity.type());
|
||||
EXPECT_EQ(kLockAddress, activity.lock_address());
|
||||
EXPECT_EQ(0U, activity.user_data().size());
|
||||
}
|
||||
{
|
||||
const Activity& activity = thread_state.activities(2);
|
||||
EXPECT_EQ(Activity::ACT_EVENT_WAIT, activity.type());
|
||||
EXPECT_EQ(kEventAddress, activity.event_address());
|
||||
ASSERT_EQ(1U, activity.user_data().size());
|
||||
ASSERT_TRUE(base::Contains(activity.user_data(), "some_int"));
|
||||
EXPECT_EQ(TypedValue::kSignedValue,
|
||||
activity.user_data().at("some_int").value_case());
|
||||
EXPECT_EQ(42, activity.user_data().at("some_int").signed_value());
|
||||
}
|
||||
{
|
||||
const Activity& activity = thread_state.activities(3);
|
||||
EXPECT_EQ(Activity::ACT_THREAD_JOIN, activity.type());
|
||||
EXPECT_EQ(kThreadId, activity.thread_id());
|
||||
EXPECT_EQ(0U, activity.user_data().size());
|
||||
}
|
||||
{
|
||||
const Activity& activity = thread_state.activities(4);
|
||||
EXPECT_EQ(Activity::ACT_PROCESS_WAIT, activity.type());
|
||||
EXPECT_EQ(kProcessId, activity.process_id());
|
||||
EXPECT_EQ(0U, activity.user_data().size());
|
||||
}
|
||||
{
|
||||
const Activity& activity = thread_state.activities(5);
|
||||
EXPECT_EQ(Activity::ACT_GENERIC, activity.type());
|
||||
EXPECT_EQ(kGenericId, activity.generic_id());
|
||||
EXPECT_EQ(kGenericData, activity.generic_data());
|
||||
EXPECT_EQ(0U, activity.user_data().size());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(StabilityReportExtractorThreadTrackerTest, CollectException) {
|
||||
const void* expected_pc = reinterpret_cast<void*>(0xCAFE);
|
||||
const void* expected_address = nullptr;
|
||||
const uint32_t expected_code = 42U;
|
||||
|
||||
// Record an exception.
|
||||
const int64_t timestamp =
|
||||
base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds();
|
||||
tracker_->RecordExceptionActivity(expected_pc, expected_address,
|
||||
base::debug::Activity::ACT_EXCEPTION,
|
||||
ActivityData::ForException(expected_code));
|
||||
|
||||
// Collect report and validate.
|
||||
StabilityReport report;
|
||||
ASSERT_EQ(SUCCESS, Extract(CreateAnalyzer(), &report));
|
||||
|
||||
// Validate the presence of the exception.
|
||||
ASSERT_NO_FATAL_FAILURE(PerformBasicReportValidation(report));
|
||||
const ThreadState& thread_state = report.process_states(0).threads(0);
|
||||
ASSERT_TRUE(thread_state.has_exception());
|
||||
const Exception& exception = thread_state.exception();
|
||||
EXPECT_EQ(expected_code, exception.code());
|
||||
EXPECT_EQ(expected_pc, reinterpret_cast<void*>(exception.program_counter()));
|
||||
EXPECT_EQ(expected_address,
|
||||
reinterpret_cast<void*>(exception.exception_address()));
|
||||
const int64_t tolerance_us = 1000ULL;
|
||||
EXPECT_LE(std::abs(timestamp - exception.time()), tolerance_us);
|
||||
}
|
||||
|
||||
TEST_F(StabilityReportExtractorThreadTrackerTest, CollectNoException) {
|
||||
// Record something.
|
||||
tracker_->PushActivity(reinterpret_cast<void*>(kTaskOrigin),
|
||||
base::debug::Activity::ACT_TASK_RUN,
|
||||
ActivityData::ForTask(kTaskSequenceNum));
|
||||
|
||||
// Collect report and validate there is no exception.
|
||||
StabilityReport report;
|
||||
ASSERT_EQ(SUCCESS, Extract(CreateAnalyzer(), &report));
|
||||
ASSERT_NO_FATAL_FAILURE(PerformBasicReportValidation(report));
|
||||
const ThreadState& thread_state = report.process_states(0).threads(0);
|
||||
ASSERT_FALSE(thread_state.has_exception());
|
||||
}
|
||||
|
||||
// Tests stability report extraction.
|
||||
class StabilityReportExtractorTest : public testing::Test {
|
||||
public:
|
||||
const int kMemorySize = 1 << 20; // 1MiB
|
||||
|
||||
StabilityReportExtractorTest() {}
|
||||
~StabilityReportExtractorTest() override {
|
||||
GlobalActivityTracker* global_tracker = GlobalActivityTracker::Get();
|
||||
if (global_tracker) {
|
||||
global_tracker->ReleaseTrackerForCurrentThreadForTesting();
|
||||
delete global_tracker;
|
||||
}
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
testing::Test::SetUp();
|
||||
|
||||
// Set up a debug file path.
|
||||
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
|
||||
debug_file_path_ = temp_dir_.GetPath().AppendASCII("debug.pma");
|
||||
}
|
||||
|
||||
std::unique_ptr<GlobalActivityAnalyzer> CreateAnalyzer() {
|
||||
return GlobalActivityAnalyzer::CreateWithFile(debug_file_path());
|
||||
}
|
||||
|
||||
const FilePath& debug_file_path() { return debug_file_path_; }
|
||||
|
||||
protected:
|
||||
base::ScopedTempDir temp_dir_;
|
||||
FilePath debug_file_path_;
|
||||
};
|
||||
|
||||
TEST_F(StabilityReportExtractorTest, LogCollection) {
|
||||
// Record some log messages.
|
||||
GlobalActivityTracker::CreateWithFile(debug_file_path(), kMemorySize, 0ULL,
|
||||
"", 3);
|
||||
GlobalActivityTracker::Get()->RecordLogMessage("hello world");
|
||||
GlobalActivityTracker::Get()->RecordLogMessage("foo bar");
|
||||
|
||||
// Collect the stability report.
|
||||
StabilityReport report;
|
||||
ASSERT_EQ(SUCCESS, Extract(CreateAnalyzer(), &report));
|
||||
|
||||
// Validate the report's log content.
|
||||
ASSERT_EQ(2, report.log_messages_size());
|
||||
ASSERT_EQ("hello world", report.log_messages(0));
|
||||
ASSERT_EQ("foo bar", report.log_messages(1));
|
||||
}
|
||||
|
||||
TEST_F(StabilityReportExtractorTest, ProcessUserDataCollection) {
|
||||
const char string1[] = "foo";
|
||||
const char string2[] = "bar";
|
||||
|
||||
// Record some process user data.
|
||||
GlobalActivityTracker::CreateWithFile(debug_file_path(), kMemorySize, 0ULL,
|
||||
"", 3);
|
||||
ActivityUserData& process_data = GlobalActivityTracker::Get()->process_data();
|
||||
ActivityUserData::Snapshot snapshot;
|
||||
ASSERT_TRUE(process_data.CreateSnapshot(&snapshot));
|
||||
ASSERT_EQ(kInternalProcessDatums, snapshot.size());
|
||||
process_data.Set("raw", "foo", 3);
|
||||
process_data.SetString("string", "bar");
|
||||
process_data.SetChar("char", '9');
|
||||
process_data.SetInt("int", -9999);
|
||||
process_data.SetUint("uint", 9999);
|
||||
process_data.SetBool("bool", true);
|
||||
process_data.SetReference("ref", string1, strlen(string1));
|
||||
process_data.SetStringReference("sref", string2);
|
||||
|
||||
// Collect the stability report.
|
||||
StabilityReport report;
|
||||
ASSERT_EQ(SUCCESS, Extract(CreateAnalyzer(), &report));
|
||||
|
||||
// We expect a single process.
|
||||
ASSERT_EQ(1, report.process_states_size());
|
||||
|
||||
// Validate the report contains the process' data.
|
||||
const auto& collected_data = report.process_states(0).data();
|
||||
ASSERT_EQ(kInternalProcessDatums + 8U, collected_data.size());
|
||||
|
||||
ASSERT_TRUE(base::Contains(collected_data, "raw"));
|
||||
EXPECT_EQ(TypedValue::kBytesValue, collected_data.at("raw").value_case());
|
||||
EXPECT_EQ("foo", collected_data.at("raw").bytes_value());
|
||||
|
||||
ASSERT_TRUE(base::Contains(collected_data, "string"));
|
||||
EXPECT_EQ(TypedValue::kStringValue, collected_data.at("string").value_case());
|
||||
EXPECT_EQ("bar", collected_data.at("string").string_value());
|
||||
|
||||
ASSERT_TRUE(base::Contains(collected_data, "char"));
|
||||
EXPECT_EQ(TypedValue::kCharValue, collected_data.at("char").value_case());
|
||||
EXPECT_EQ("9", collected_data.at("char").char_value());
|
||||
|
||||
ASSERT_TRUE(base::Contains(collected_data, "int"));
|
||||
EXPECT_EQ(TypedValue::kSignedValue, collected_data.at("int").value_case());
|
||||
EXPECT_EQ(-9999, collected_data.at("int").signed_value());
|
||||
|
||||
ASSERT_TRUE(base::Contains(collected_data, "uint"));
|
||||
EXPECT_EQ(TypedValue::kUnsignedValue, collected_data.at("uint").value_case());
|
||||
EXPECT_EQ(9999U, collected_data.at("uint").unsigned_value());
|
||||
|
||||
ASSERT_TRUE(base::Contains(collected_data, "bool"));
|
||||
EXPECT_EQ(TypedValue::kBoolValue, collected_data.at("bool").value_case());
|
||||
EXPECT_TRUE(collected_data.at("bool").bool_value());
|
||||
|
||||
ASSERT_TRUE(base::Contains(collected_data, "ref"));
|
||||
EXPECT_EQ(TypedValue::kBytesReference, collected_data.at("ref").value_case());
|
||||
const TypedValue::Reference& ref = collected_data.at("ref").bytes_reference();
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(string1), ref.address());
|
||||
EXPECT_EQ(strlen(string1), static_cast<uint64_t>(ref.size()));
|
||||
|
||||
ASSERT_TRUE(base::Contains(collected_data, "sref"));
|
||||
EXPECT_EQ(TypedValue::kStringReference,
|
||||
collected_data.at("sref").value_case());
|
||||
const TypedValue::Reference& sref =
|
||||
collected_data.at("sref").string_reference();
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(string2), sref.address());
|
||||
EXPECT_EQ(strlen(string2), static_cast<uint64_t>(sref.size()));
|
||||
}
|
||||
|
||||
TEST_F(StabilityReportExtractorTest, ModuleCollection) {
|
||||
// Record some module information.
|
||||
GlobalActivityTracker::CreateWithFile(debug_file_path(), kMemorySize, 0ULL,
|
||||
"", 3);
|
||||
|
||||
base::debug::GlobalActivityTracker::ModuleInfo module_info = {};
|
||||
module_info.is_loaded = true;
|
||||
module_info.address = 0x123456;
|
||||
module_info.load_time = 1111LL;
|
||||
module_info.size = 0x2d000;
|
||||
module_info.timestamp = 0xCAFECAFE;
|
||||
module_info.age = 1;
|
||||
crashpad::UUID debug_uuid;
|
||||
debug_uuid.InitializeFromString("11223344-5566-7788-abcd-0123456789ab");
|
||||
memcpy(module_info.identifier, &debug_uuid, sizeof(module_info.identifier));
|
||||
module_info.file = "foo";
|
||||
module_info.debug_file = "bar";
|
||||
|
||||
GlobalActivityTracker::Get()->RecordModuleInfo(module_info);
|
||||
|
||||
// Collect the stability report.
|
||||
StabilityReport report;
|
||||
ASSERT_EQ(SUCCESS, Extract(CreateAnalyzer(), &report));
|
||||
|
||||
// Validate the report's modules content.
|
||||
ASSERT_EQ(1, report.process_states_size());
|
||||
const ProcessState& process_state = report.process_states(0);
|
||||
ASSERT_EQ(1, process_state.modules_size());
|
||||
|
||||
const CodeModule collected_module = process_state.modules(0);
|
||||
EXPECT_EQ(module_info.address,
|
||||
static_cast<uintptr_t>(collected_module.base_address()));
|
||||
EXPECT_EQ(module_info.size, static_cast<size_t>(collected_module.size()));
|
||||
EXPECT_EQ(module_info.file, collected_module.code_file());
|
||||
EXPECT_EQ("CAFECAFE2d000", collected_module.code_identifier());
|
||||
EXPECT_EQ(module_info.debug_file, collected_module.debug_file());
|
||||
EXPECT_EQ("1122334455667788ABCD0123456789AB1",
|
||||
collected_module.debug_identifier());
|
||||
EXPECT_EQ("", collected_module.version());
|
||||
EXPECT_EQ(0LL, collected_module.shrink_down_delta());
|
||||
EXPECT_EQ(!module_info.is_loaded, collected_module.is_unloaded());
|
||||
}
|
||||
|
||||
} // namespace browser_watcher
|
@ -1,298 +0,0 @@
|
||||
// Copyright 2017 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "components/browser_watcher/activity_report_user_stream_data_source.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
// Must be included after windows.h.
|
||||
#include <psapi.h>
|
||||
|
||||
#include "base/files/file.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/memory/free_deleter.h"
|
||||
#include "base/metrics/histogram_functions.h"
|
||||
#include "base/metrics/persistent_memory_allocator.h"
|
||||
#include "base/process/memory.h"
|
||||
#include "base/process/process.h"
|
||||
#include "base/time/time.h"
|
||||
#include "components/browser_watcher/activity_report_extractor.h"
|
||||
#include "components/browser_watcher/activity_tracker_annotation.h"
|
||||
#include "components/browser_watcher/extended_crash_reporting_metrics.h"
|
||||
#include "components/browser_watcher/minidump_user_streams.h"
|
||||
#include "third_party/crashpad/crashpad/minidump/minidump_user_extension_stream_data_source.h"
|
||||
#include "third_party/crashpad/crashpad/snapshot/annotation_snapshot.h"
|
||||
#include "third_party/crashpad/crashpad/snapshot/exception_snapshot.h"
|
||||
#include "third_party/crashpad/crashpad/snapshot/module_snapshot.h"
|
||||
#include "third_party/crashpad/crashpad/snapshot/process_snapshot.h"
|
||||
#include "third_party/crashpad/crashpad/util/process/process_memory.h"
|
||||
|
||||
namespace browser_watcher {
|
||||
|
||||
namespace {
|
||||
|
||||
// TODO(siggi): Refactor this to harmonize with the activity tracker setup.
|
||||
const size_t kMaxActivityAnnotationSize = 2 << 20;
|
||||
|
||||
using UniqueMallocPtr = std::unique_ptr<void, base::FreeDeleter>;
|
||||
|
||||
UniqueMallocPtr UncheckedAllocate(size_t size) {
|
||||
void* raw_ptr = nullptr;
|
||||
if (!base::UncheckedMalloc(size, &raw_ptr))
|
||||
return UniqueMallocPtr();
|
||||
|
||||
return UniqueMallocPtr(raw_ptr);
|
||||
}
|
||||
|
||||
// A PersistentMemoryAllocator subclass that can take ownership of a buffer
|
||||
// that's allocated with a malloc-compatible allocation function.
|
||||
class MallocMemoryAllocator : public base::PersistentMemoryAllocator {
|
||||
public:
|
||||
MallocMemoryAllocator(UniqueMallocPtr buffer, size_t size);
|
||||
~MallocMemoryAllocator() override;
|
||||
};
|
||||
|
||||
MallocMemoryAllocator::MallocMemoryAllocator(UniqueMallocPtr buffer,
|
||||
size_t size)
|
||||
: base::PersistentMemoryAllocator(buffer.release(), size, 0, 0, "", true) {}
|
||||
|
||||
MallocMemoryAllocator::~MallocMemoryAllocator() {
|
||||
free(const_cast<char*>(mem_base_));
|
||||
}
|
||||
|
||||
class BufferExtensionStreamDataSource final
|
||||
: public crashpad::MinidumpUserExtensionStreamDataSource {
|
||||
public:
|
||||
explicit BufferExtensionStreamDataSource(uint32_t stream_type);
|
||||
|
||||
BufferExtensionStreamDataSource(const BufferExtensionStreamDataSource&) =
|
||||
delete;
|
||||
BufferExtensionStreamDataSource& operator=(
|
||||
const BufferExtensionStreamDataSource&) = delete;
|
||||
|
||||
bool Init(const StabilityReport& report);
|
||||
|
||||
size_t StreamDataSize() override;
|
||||
bool ReadStreamData(Delegate* delegate) override;
|
||||
|
||||
private:
|
||||
std::string data_;
|
||||
};
|
||||
|
||||
BufferExtensionStreamDataSource::BufferExtensionStreamDataSource(
|
||||
uint32_t stream_type)
|
||||
: crashpad::MinidumpUserExtensionStreamDataSource(stream_type) {}
|
||||
|
||||
bool BufferExtensionStreamDataSource::Init(const StabilityReport& report) {
|
||||
if (report.SerializeToString(&data_))
|
||||
return true;
|
||||
data_.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t BufferExtensionStreamDataSource::StreamDataSize() {
|
||||
DCHECK(!data_.empty());
|
||||
return data_.size();
|
||||
}
|
||||
|
||||
bool BufferExtensionStreamDataSource::ReadStreamData(Delegate* delegate) {
|
||||
DCHECK(!data_.empty());
|
||||
return delegate->ExtensionStreamDataSourceRead(
|
||||
data_.size() ? data_.data() : nullptr, data_.size());
|
||||
}
|
||||
|
||||
// TODO(manzagop): Collection should factor in whether this is a true crash or
|
||||
// dump without crashing.
|
||||
bool CollectStabilityReport(
|
||||
std::unique_ptr<base::debug::GlobalActivityAnalyzer> global_analyzer,
|
||||
StabilityReport* report) {
|
||||
CollectionStatus status = ANALYZER_CREATION_FAILED;
|
||||
if (global_analyzer)
|
||||
status = Extract(std::move(global_analyzer), report);
|
||||
|
||||
base::UmaHistogramEnumeration("ActivityTracker.CollectCrash.Status", status,
|
||||
COLLECTION_STATUS_MAX);
|
||||
if (status != SUCCESS)
|
||||
return false;
|
||||
|
||||
LogCollectOnCrashEvent(CollectOnCrashEvent::kReportExtractionSuccess);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CollectSystemPerformanceMetrics(StabilityReport* report) {
|
||||
// Grab system commit memory. Also best effort.
|
||||
PERFORMANCE_INFORMATION perf_info = {sizeof(perf_info)};
|
||||
if (GetPerformanceInfo(&perf_info, sizeof(perf_info))) {
|
||||
auto* memory_state =
|
||||
report->mutable_system_memory_state()->mutable_windows_memory();
|
||||
|
||||
memory_state->set_system_commit_limit(perf_info.CommitLimit);
|
||||
memory_state->set_system_commit_remaining(perf_info.CommitLimit -
|
||||
perf_info.CommitTotal);
|
||||
memory_state->set_system_handle_count(perf_info.HandleCount);
|
||||
}
|
||||
}
|
||||
|
||||
void CollectProcessPerformanceMetrics(
|
||||
crashpad::ProcessSnapshot* process_snapshot,
|
||||
StabilityReport* report) {
|
||||
const crashpad::ExceptionSnapshot* exception = process_snapshot->Exception();
|
||||
|
||||
if (!exception)
|
||||
return;
|
||||
|
||||
// Find or create the ProcessState for the process in question.
|
||||
base::ProcessId pid = process_snapshot->ProcessID();
|
||||
ProcessState* process_state = nullptr;
|
||||
for (int i = 0; i < report->process_states_size(); ++i) {
|
||||
ProcessState* temp = report->mutable_process_states(i);
|
||||
|
||||
if (temp->has_process_id() && temp->process_id() == pid) {
|
||||
process_state = temp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!process_state) {
|
||||
process_state = report->add_process_states();
|
||||
process_state->set_process_id(pid);
|
||||
}
|
||||
|
||||
auto* memory_state =
|
||||
process_state->mutable_memory_state()->mutable_windows_memory();
|
||||
|
||||
// Grab the requested allocation size in case of OOM exception.
|
||||
if (exception->Exception() == base::win::kOomExceptionCode) {
|
||||
const auto& codes = exception->Codes();
|
||||
if (codes.size()) {
|
||||
// The first parameter, if present, is the size of the allocation attempt.
|
||||
memory_state->set_process_allocation_attempt(codes[0]);
|
||||
}
|
||||
}
|
||||
|
||||
base::Process process(base::Process::OpenWithAccess(
|
||||
pid, PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ));
|
||||
|
||||
if (process.IsValid()) {
|
||||
PROCESS_MEMORY_COUNTERS_EX process_memory = {sizeof(process_memory)};
|
||||
if (::GetProcessMemoryInfo(
|
||||
process.Handle(),
|
||||
reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&process_memory),
|
||||
sizeof(process_memory))) {
|
||||
// This is in units of bytes, re-scale to pages for consistency with
|
||||
// system metrics.
|
||||
const uint64_t kPageSize = 4096;
|
||||
memory_state->set_process_private_usage(process_memory.PrivateUsage /
|
||||
kPageSize);
|
||||
memory_state->set_process_peak_workingset_size(
|
||||
process_memory.PeakWorkingSetSize / kPageSize);
|
||||
memory_state->set_process_peak_pagefile_usage(
|
||||
process_memory.PeakPagefileUsage / kPageSize);
|
||||
}
|
||||
|
||||
DWORD process_handle_count = 0;
|
||||
if (::GetProcessHandleCount(process.Handle(), &process_handle_count)) {
|
||||
memory_state->set_process_handle_count(process_handle_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the process has a beacon for in-memory activities, returns an analyzer
|
||||
// for it.
|
||||
std::unique_ptr<base::debug::GlobalActivityAnalyzer>
|
||||
MaybeGetInMemoryActivityAnalyzer(crashpad::ProcessSnapshot* process_snapshot) {
|
||||
if (!process_snapshot->Memory())
|
||||
return nullptr;
|
||||
|
||||
auto modules = process_snapshot->Modules();
|
||||
for (auto* module : modules) {
|
||||
auto annotations = module->AnnotationObjects();
|
||||
for (const auto& annotation : annotations) {
|
||||
if (annotation.name == ActivityTrackerAnnotation::kAnnotationName &&
|
||||
annotation.type == static_cast<uint16_t>(
|
||||
ActivityTrackerAnnotation::kAnnotationType) &&
|
||||
annotation.value.size() ==
|
||||
sizeof(ActivityTrackerAnnotation::ValueType)) {
|
||||
// Re-cast the annotation to its value type.
|
||||
ActivityTrackerAnnotation::ValueType value;
|
||||
memcpy(&value, annotation.value.data(), sizeof(value));
|
||||
|
||||
// Check the size field for sanity.
|
||||
if (value.size > kMaxActivityAnnotationSize)
|
||||
continue;
|
||||
|
||||
// Allocate the buffer with no terminate-on-exhaustion to make sure
|
||||
// this can't be used to bring down the handler and thus elide
|
||||
// crash reporting.
|
||||
UniqueMallocPtr buffer = UncheckedAllocate(value.size);
|
||||
if (!buffer || !base::PersistentMemoryAllocator::IsMemoryAcceptable(
|
||||
buffer.get(), value.size, 0, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read the activity tracker data from the crashed process.
|
||||
if (process_snapshot->Memory()->Read(value.address, value.size,
|
||||
buffer.get())) {
|
||||
// Success - wrap an allocator on the buffer, and an analyzer on that.
|
||||
std::unique_ptr<MallocMemoryAllocator> allocator =
|
||||
std::make_unique<MallocMemoryAllocator>(std::move(buffer),
|
||||
value.size);
|
||||
|
||||
return base::debug::GlobalActivityAnalyzer::CreateWithAllocator(
|
||||
std::move(allocator));
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ActivityReportUserStreamDataSource::ActivityReportUserStreamDataSource(
|
||||
const base::FilePath& user_data_dir)
|
||||
: user_data_dir_(user_data_dir) {}
|
||||
|
||||
std::unique_ptr<crashpad::MinidumpUserExtensionStreamDataSource>
|
||||
ActivityReportUserStreamDataSource::ProduceStreamData(
|
||||
crashpad::ProcessSnapshot* process_snapshot) {
|
||||
DCHECK(process_snapshot);
|
||||
LogCollectOnCrashEvent(CollectOnCrashEvent::kCollectAttempt);
|
||||
|
||||
StabilityReport report;
|
||||
|
||||
// See whether there's an activity tracking report beacon in the process'
|
||||
// annotations.
|
||||
std::unique_ptr<base::debug::GlobalActivityAnalyzer> global_analyzer =
|
||||
MaybeGetInMemoryActivityAnalyzer(process_snapshot);
|
||||
bool collected_report = false;
|
||||
if (global_analyzer) {
|
||||
LogCollectOnCrashEvent(CollectOnCrashEvent::kInMemoryAnnotationExists);
|
||||
|
||||
collected_report =
|
||||
CollectStabilityReport(std::move(global_analyzer), &report);
|
||||
}
|
||||
|
||||
CollectSystemPerformanceMetrics(&report);
|
||||
CollectProcessPerformanceMetrics(process_snapshot, &report);
|
||||
|
||||
std::unique_ptr<BufferExtensionStreamDataSource> source(
|
||||
new BufferExtensionStreamDataSource(kActivityReportStreamType));
|
||||
if (!source->Init(report))
|
||||
return nullptr;
|
||||
|
||||
if (collected_report)
|
||||
LogCollectOnCrashEvent(CollectOnCrashEvent::kSuccess);
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
} // namespace browser_watcher
|
@ -1,42 +0,0 @@
|
||||
// Copyright 2017 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef COMPONENTS_BROWSER_WATCHER_ACTIVITY_REPORT_USER_STREAM_DATA_SOURCE_H_
|
||||
#define COMPONENTS_BROWSER_WATCHER_ACTIVITY_REPORT_USER_STREAM_DATA_SOURCE_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "third_party/crashpad/crashpad/handler/user_stream_data_source.h"
|
||||
|
||||
namespace crashpad {
|
||||
class MinidumpUserExtensionStreamDataSource;
|
||||
class ProcessSnapshot;
|
||||
} // namespace crashpad
|
||||
|
||||
namespace browser_watcher {
|
||||
|
||||
// Collects stability instrumentation corresponding to a ProcessSnapshot and
|
||||
// makes it available to the crash handler.
|
||||
class ActivityReportUserStreamDataSource
|
||||
: public crashpad::UserStreamDataSource {
|
||||
public:
|
||||
explicit ActivityReportUserStreamDataSource(
|
||||
const base::FilePath& user_data_dir);
|
||||
|
||||
ActivityReportUserStreamDataSource(
|
||||
const ActivityReportUserStreamDataSource&) = delete;
|
||||
ActivityReportUserStreamDataSource& operator=(
|
||||
const ActivityReportUserStreamDataSource&) = delete;
|
||||
|
||||
std::unique_ptr<crashpad::MinidumpUserExtensionStreamDataSource>
|
||||
ProduceStreamData(crashpad::ProcessSnapshot* process_snapshot) override;
|
||||
|
||||
private:
|
||||
base::FilePath user_data_dir_;
|
||||
};
|
||||
|
||||
} // namespace browser_watcher
|
||||
|
||||
#endif // COMPONENTS_BROWSER_WATCHER_ACTIVITY_REPORT_USER_STREAM_DATA_SOURCE_H_
|
@ -1,28 +0,0 @@
|
||||
// Copyright 2020 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "components/browser_watcher/activity_tracker_annotation.h"
|
||||
|
||||
namespace browser_watcher {
|
||||
|
||||
const char ActivityTrackerAnnotation::kAnnotationName[] =
|
||||
"ActivityTrackerLocation";
|
||||
|
||||
ActivityTrackerAnnotation::ActivityTrackerAnnotation()
|
||||
: crashpad::Annotation(kAnnotationType, kAnnotationName, &value_) {}
|
||||
|
||||
void ActivityTrackerAnnotation::SetValue(const void* address, size_t size) {
|
||||
value_.address = reinterpret_cast<uint64_t>(address);
|
||||
value_.size = size;
|
||||
SetSize(sizeof(value_));
|
||||
}
|
||||
|
||||
// static
|
||||
ActivityTrackerAnnotation* ActivityTrackerAnnotation::GetInstance() {
|
||||
// This object is intentionally leaked.
|
||||
static ActivityTrackerAnnotation* instance = new ActivityTrackerAnnotation();
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // namespace browser_watcher
|
@ -1,39 +0,0 @@
|
||||
// Copyright 2020 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef COMPONENTS_BROWSER_WATCHER_ACTIVITY_TRACKER_ANNOTATION_H_
|
||||
#define COMPONENTS_BROWSER_WATCHER_ACTIVITY_TRACKER_ANNOTATION_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "third_party/crashpad/crashpad/client/annotation.h"
|
||||
|
||||
namespace browser_watcher {
|
||||
|
||||
// A Crashpad annotation to store the location and size of the buffer used
|
||||
// for activity tracking. This is used to retrieve and record tracked activities
|
||||
// from the handler at crash time.
|
||||
class ActivityTrackerAnnotation : public crashpad::Annotation {
|
||||
public:
|
||||
struct ValueType {
|
||||
uint64_t address;
|
||||
uint64_t size;
|
||||
};
|
||||
static constexpr Type kAnnotationType = Annotation::UserDefinedType(0xBAB);
|
||||
static const char kAnnotationName[];
|
||||
|
||||
void SetValue(const void* address, size_t size);
|
||||
|
||||
// Returns the sole instance of this class.
|
||||
static ActivityTrackerAnnotation* GetInstance();
|
||||
|
||||
private:
|
||||
ActivityTrackerAnnotation();
|
||||
|
||||
ValueType value_;
|
||||
};
|
||||
|
||||
} // namespace browser_watcher
|
||||
|
||||
#endif // COMPONENTS_BROWSER_WATCHER_ACTIVITY_TRACKER_ANNOTATION_H_
|
@ -1,38 +0,0 @@
|
||||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "components/browser_watcher/activity_tracker_annotation.h"
|
||||
|
||||
#include "components/crash/core/common/crash_key.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace browser_watcher {
|
||||
|
||||
class ActivityTrackerAnnotationTest : public testing::Test {
|
||||
public:
|
||||
void SetUp() override { crash_reporter::InitializeCrashKeysForTesting(); }
|
||||
void TearDown() override { crash_reporter::ResetCrashKeysForTesting(); }
|
||||
};
|
||||
|
||||
TEST_F(ActivityTrackerAnnotationTest, RegistersOnFirstSet) {
|
||||
static const char* kBuffer[128];
|
||||
ActivityTrackerAnnotation* annotation =
|
||||
ActivityTrackerAnnotation::GetInstance();
|
||||
// Validate that the annotation doesn't register on construction.
|
||||
EXPECT_EQ("", crash_reporter::GetCrashKeyValue(
|
||||
ActivityTrackerAnnotation::kAnnotationName));
|
||||
|
||||
annotation->SetValue(&kBuffer, sizeof(kBuffer));
|
||||
std::string string_value = crash_reporter::GetCrashKeyValue(
|
||||
ActivityTrackerAnnotation::kAnnotationName);
|
||||
ASSERT_EQ(sizeof(ActivityTrackerAnnotation::ValueType), string_value.size());
|
||||
|
||||
ActivityTrackerAnnotation::ValueType value = {};
|
||||
memcpy(&value, string_value.data(), sizeof(value));
|
||||
|
||||
EXPECT_EQ(value.address, reinterpret_cast<uint64_t>(&kBuffer));
|
||||
EXPECT_EQ(value.size, sizeof(kBuffer));
|
||||
}
|
||||
|
||||
} // namespace browser_watcher
|
@ -1,299 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// A utility for printing the contents of a postmortem stability minidump.
|
||||
|
||||
#include <windows.h> // NOLINT
|
||||
|
||||
#include <dbghelp.h>
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/files/scoped_file.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "components/browser_watcher/activity_report.pb.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const char kUsage[] =
|
||||
"Usage: %ls --minidump=<minidump file>\n"
|
||||
"\n"
|
||||
" Dumps the contents of a postmortem minidump in a human readable way.\n";
|
||||
|
||||
bool ParseCommandLine(const base::CommandLine* cmd,
|
||||
base::FilePath* minidump_path) {
|
||||
*minidump_path = cmd->GetSwitchValuePath("minidump");
|
||||
if (minidump_path->empty()) {
|
||||
LOG(ERROR) << "Missing minidump file.\n";
|
||||
LOG(ERROR) << base::StringPrintf(kUsage, cmd->GetProgram().value().c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Indent(FILE* out, int indent_level) {
|
||||
DCHECK(out);
|
||||
for (int i = 0; i < indent_level; ++i)
|
||||
fprintf(out, " ");
|
||||
}
|
||||
|
||||
void PrintUserData(
|
||||
FILE* out,
|
||||
int indent_level,
|
||||
const google::protobuf::Map<std::string, browser_watcher::TypedValue>&
|
||||
user_data) {
|
||||
DCHECK(out);
|
||||
Indent(out, indent_level);
|
||||
fprintf(out, "User data (%zu)\n", user_data.size());
|
||||
for (const auto& kv : user_data) {
|
||||
Indent(out, indent_level + 1);
|
||||
fprintf(out, "%s : ", kv.first.c_str());
|
||||
const browser_watcher::TypedValue& value = kv.second;
|
||||
switch (kv.second.value_case()) {
|
||||
case browser_watcher::TypedValue::kBytesValue: {
|
||||
const std::string& bytes_value = value.bytes_value();
|
||||
for (size_t i = 0; i < bytes_value.size(); ++i)
|
||||
fprintf(out, "%02X ", bytes_value.at(i));
|
||||
fprintf(out, "\n");
|
||||
break;
|
||||
}
|
||||
case browser_watcher::TypedValue::kBytesReference:
|
||||
fprintf(out, "bytes reference (address: %llX, size: %llX)\n",
|
||||
value.bytes_reference().address(),
|
||||
value.bytes_reference().size());
|
||||
break;
|
||||
case browser_watcher::TypedValue::kStringValue:
|
||||
fprintf(out, "\"%s\"\n", value.string_value().c_str());
|
||||
break;
|
||||
case browser_watcher::TypedValue::kStringReference:
|
||||
fprintf(out, "string reference (address: %llX, size: %llX)\n",
|
||||
value.string_reference().address(),
|
||||
value.string_reference().size());
|
||||
break;
|
||||
case browser_watcher::TypedValue::kCharValue:
|
||||
fprintf(out, "'%s'\n", value.char_value().c_str());
|
||||
break;
|
||||
case browser_watcher::TypedValue::kBoolValue:
|
||||
fprintf(out, "%s\n", value.bool_value() ? "true" : "false");
|
||||
break;
|
||||
case browser_watcher::TypedValue::kSignedValue:
|
||||
fprintf(out, "%lld\n", value.signed_value());
|
||||
break;
|
||||
case browser_watcher::TypedValue::kUnsignedValue:
|
||||
fprintf(out, "%llu\n", value.unsigned_value());
|
||||
break;
|
||||
case browser_watcher::TypedValue::VALUE_NOT_SET:
|
||||
fprintf(out, "<not set>\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PrintActivity(FILE* out,
|
||||
int indent_level,
|
||||
const browser_watcher::Activity& activity) {
|
||||
DCHECK(out);
|
||||
Indent(out, indent_level);
|
||||
fprintf(out, "Activity\n");
|
||||
Indent(out, indent_level + 1);
|
||||
fprintf(out, "type: %d\n", activity.type());
|
||||
Indent(out, indent_level + 1);
|
||||
fprintf(out, "time: %lld\n", activity.time());
|
||||
Indent(out, indent_level + 1);
|
||||
fprintf(out, "address: %llX\n", activity.address());
|
||||
switch (activity.type()) {
|
||||
case browser_watcher::Activity::UNKNOWN:
|
||||
break;
|
||||
case browser_watcher::Activity::ACT_TASK_RUN:
|
||||
Indent(out, indent_level + 1);
|
||||
fprintf(out, "origin_address: %llX\n", activity.origin_address());
|
||||
fprintf(out, "task_sequence_id: %lld\n", activity.task_sequence_id());
|
||||
break;
|
||||
case browser_watcher::Activity::ACT_LOCK_ACQUIRE:
|
||||
Indent(out, indent_level + 1);
|
||||
fprintf(out, "lock_address: %llX\n", activity.lock_address());
|
||||
break;
|
||||
case browser_watcher::Activity::ACT_EVENT_WAIT:
|
||||
Indent(out, indent_level + 1);
|
||||
fprintf(out, "event_address: %llX\n", activity.event_address());
|
||||
break;
|
||||
case browser_watcher::Activity::ACT_THREAD_JOIN:
|
||||
Indent(out, indent_level + 1);
|
||||
fprintf(out, "thread_id: %lld\n", activity.thread_id());
|
||||
break;
|
||||
case browser_watcher::Activity::ACT_PROCESS_WAIT:
|
||||
Indent(out, indent_level + 1);
|
||||
fprintf(out, "process_id: %lld\n", activity.process_id());
|
||||
break;
|
||||
case browser_watcher::Activity::ACT_GENERIC:
|
||||
Indent(out, indent_level + 1);
|
||||
fprintf(out, "id: %u, data: %d\n", activity.generic_id(),
|
||||
activity.generic_data());
|
||||
break;
|
||||
}
|
||||
|
||||
PrintUserData(out, indent_level + 1, activity.user_data());
|
||||
}
|
||||
|
||||
void PrintProcessState(FILE* out,
|
||||
const browser_watcher::ProcessState& process) {
|
||||
std::string process_type;
|
||||
switch (process.process_type()) {
|
||||
case browser_watcher::ProcessState::UNKNOWN_PROCESS:
|
||||
process_type = "unknown type";
|
||||
break;
|
||||
case browser_watcher::ProcessState::BROWSER_PROCESS:
|
||||
process_type = "browser";
|
||||
break;
|
||||
case browser_watcher::ProcessState::WATCHER_PROCESS:
|
||||
process_type = "watcher";
|
||||
break;
|
||||
default:
|
||||
base::SStringPrintf(&process_type, "process type %d",
|
||||
process.process_type());
|
||||
break;
|
||||
}
|
||||
|
||||
fprintf(out, "Process %lld (%s, %d threads)\n", process.process_id(),
|
||||
process_type.c_str(), process.threads_size());
|
||||
|
||||
if (process.has_memory_state() &&
|
||||
process.memory_state().has_windows_memory()) {
|
||||
const auto& windows_memory = process.memory_state().windows_memory();
|
||||
if (windows_memory.has_process_private_usage()) {
|
||||
fprintf(out, "process_private_usage: %u pages\n",
|
||||
windows_memory.process_private_usage());
|
||||
}
|
||||
if (windows_memory.has_process_peak_workingset_size()) {
|
||||
fprintf(out, "process_peak_workingset_size: %u pages\n",
|
||||
windows_memory.process_peak_workingset_size());
|
||||
}
|
||||
if (windows_memory.has_process_peak_pagefile_usage()) {
|
||||
fprintf(out, "process_peak_pagefile_usage: %u pages\n",
|
||||
windows_memory.process_peak_pagefile_usage());
|
||||
}
|
||||
if (windows_memory.has_process_allocation_attempt()) {
|
||||
fprintf(out, "process_allocation_attempt: %u bytes\n",
|
||||
windows_memory.process_allocation_attempt());
|
||||
}
|
||||
if (windows_memory.has_process_handle_count()) {
|
||||
fprintf(out, "process_handle_count: %u handles\n",
|
||||
windows_memory.process_handle_count());
|
||||
}
|
||||
}
|
||||
|
||||
for (const browser_watcher::ThreadState& thread : process.threads()) {
|
||||
fprintf(out, "Thread %lld (%s) : %d activities\n", thread.thread_id(),
|
||||
thread.thread_name().c_str(), thread.activity_count());
|
||||
for (const browser_watcher::Activity& activity : thread.activities())
|
||||
PrintActivity(out, 1, activity);
|
||||
}
|
||||
|
||||
PrintUserData(out, 1, process.data());
|
||||
}
|
||||
|
||||
// TODO(manzagop): flesh out as StabilityReport gets fleshed out.
|
||||
void PrintReport(FILE* out, const browser_watcher::StabilityReport& report) {
|
||||
for (std::string message : report.log_messages())
|
||||
fprintf(out, "log message:\n%s\n", message.c_str());
|
||||
|
||||
if (report.has_system_memory_state() &&
|
||||
report.system_memory_state().has_windows_memory()) {
|
||||
const auto& windows_memory = report.system_memory_state().windows_memory();
|
||||
|
||||
if (windows_memory.has_system_commit_limit()) {
|
||||
fprintf(out, "system_commit_limit: %u pages\n",
|
||||
windows_memory.system_commit_limit());
|
||||
}
|
||||
if (windows_memory.has_system_commit_remaining()) {
|
||||
fprintf(out, "system_commit_remaining: %u pages\n",
|
||||
windows_memory.system_commit_remaining());
|
||||
}
|
||||
if (windows_memory.has_system_handle_count()) {
|
||||
fprintf(out, "system_handle_count: %u handles\n",
|
||||
windows_memory.system_handle_count());
|
||||
}
|
||||
}
|
||||
PrintUserData(out, 0, report.global_data());
|
||||
for (int i = 0; i < report.process_states_size(); ++i) {
|
||||
const browser_watcher::ProcessState process = report.process_states(i);
|
||||
PrintProcessState(out, process);
|
||||
}
|
||||
}
|
||||
|
||||
bool GetStabilityStreamRvaAndSize(RVA directory_rva,
|
||||
ULONG32 stream_count,
|
||||
FILE* file,
|
||||
RVA* report_rva,
|
||||
ULONG32* report_size_bytes) {
|
||||
std::vector<MINIDUMP_DIRECTORY> directory;
|
||||
directory.resize(stream_count);
|
||||
|
||||
CHECK_EQ(0, fseek(file, directory_rva, SEEK_SET));
|
||||
CHECK_EQ(stream_count, fread(directory.data(), sizeof(MINIDUMP_DIRECTORY),
|
||||
stream_count, file));
|
||||
|
||||
for (const MINIDUMP_DIRECTORY& entry : directory) {
|
||||
constexpr ULONG32 kActivityStream = static_cast<ULONG32>(0x4B6B0002);
|
||||
if (entry.StreamType == kActivityStream) {
|
||||
*report_rva = entry.Location.Rva;
|
||||
*report_size_bytes = entry.Location.DataSize;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int Main(int argc, char** argv) {
|
||||
base::CommandLine::Init(argc, argv);
|
||||
|
||||
// Get the dump.
|
||||
base::FilePath minidump_path;
|
||||
if (!ParseCommandLine(base::CommandLine::ForCurrentProcess(), &minidump_path))
|
||||
return 1;
|
||||
|
||||
// Read the minidump to extract the proto.
|
||||
base::ScopedFILE minidump_file;
|
||||
minidump_file.reset(base::OpenFile(minidump_path, "rb"));
|
||||
CHECK(minidump_file.get());
|
||||
|
||||
// Read the header.
|
||||
// TODO(manzagop): leverage Crashpad to do this.
|
||||
MINIDUMP_HEADER header = {};
|
||||
CHECK_EQ(1U, fread(&header, sizeof(header), 1U, minidump_file.get()));
|
||||
CHECK_EQ(static_cast<ULONG32>(MINIDUMP_SIGNATURE), header.Signature);
|
||||
fprintf(stdout, "Number of streams: %u\n", header.NumberOfStreams);
|
||||
RVA directory_rva = header.StreamDirectoryRva;
|
||||
|
||||
RVA report_rva;
|
||||
ULONG32 report_size_bytes;
|
||||
CHECK(GetStabilityStreamRvaAndSize(directory_rva, header.NumberOfStreams,
|
||||
minidump_file.get(), &report_rva,
|
||||
&report_size_bytes));
|
||||
|
||||
// Read the serialized stability report.
|
||||
std::string serialized_report;
|
||||
serialized_report.resize(report_size_bytes);
|
||||
CHECK_EQ(0, fseek(minidump_file.get(), report_rva, SEEK_SET));
|
||||
CHECK_EQ(report_size_bytes, fread(&serialized_report.at(0), 1,
|
||||
report_size_bytes, minidump_file.get()));
|
||||
|
||||
browser_watcher::StabilityReport report;
|
||||
CHECK(report.ParseFromString(serialized_report));
|
||||
|
||||
// Note: we can't use the usual protocol buffer human readable API due to
|
||||
// the use of optimize_for = LITE_RUNTIME.
|
||||
PrintReport(stdout, report);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
return Main(argc, argv);
|
||||
}
|
@ -1,243 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "components/browser_watcher/extended_crash_reporting.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/debug/activity_tracker.h"
|
||||
#include "base/memory/ptr_util.h"
|
||||
#include "base/metrics/persistent_memory_allocator.h"
|
||||
#include "base/strings/string_piece.h"
|
||||
#include "base/time/time.h"
|
||||
#include "base/win/pe_image.h"
|
||||
#include "build/build_config.h"
|
||||
#include "components/browser_watcher/activity_data_names.h"
|
||||
#include "components/browser_watcher/activity_report.pb.h"
|
||||
#include "components/browser_watcher/activity_tracker_annotation.h"
|
||||
#include "components/browser_watcher/extended_crash_reporting_metrics.h"
|
||||
#include "components/browser_watcher/features.h"
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
// https://devblogs.microsoft.com/oldnewthing/20041025-00/?p=37483.
|
||||
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||
#endif
|
||||
|
||||
namespace browser_watcher {
|
||||
|
||||
namespace {
|
||||
|
||||
ExtendedCrashReporting* g_instance = nullptr;
|
||||
|
||||
uintptr_t GetProgramCounter(const CONTEXT& context) {
|
||||
#if defined(ARCH_CPU_X86)
|
||||
return context.Eip;
|
||||
#elif defined(ARCH_CPU_X86_64)
|
||||
return context.Rip;
|
||||
#elif defined(ARCH_CPU_ARM64)
|
||||
return context.Pc;
|
||||
#endif
|
||||
}
|
||||
|
||||
LONG CALLBACK VectoredExceptionHandler(EXCEPTION_POINTERS* exception_pointers) {
|
||||
base::debug::GlobalActivityTracker* tracker =
|
||||
base::debug::GlobalActivityTracker::Get();
|
||||
if (tracker) {
|
||||
EXCEPTION_RECORD* record = exception_pointers->ExceptionRecord;
|
||||
uintptr_t pc = GetProgramCounter(*exception_pointers->ContextRecord);
|
||||
tracker->RecordException(reinterpret_cast<void*>(pc),
|
||||
record->ExceptionAddress, record->ExceptionCode);
|
||||
}
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH; // Continue to the next handler.
|
||||
}
|
||||
|
||||
// Record information about the chrome module.
|
||||
void RecordChromeModuleInfo(
|
||||
base::debug::GlobalActivityTracker* global_tracker) {
|
||||
DCHECK(global_tracker);
|
||||
|
||||
base::debug::GlobalActivityTracker::ModuleInfo module;
|
||||
module.is_loaded = true;
|
||||
module.address = reinterpret_cast<uintptr_t>(&__ImageBase);
|
||||
|
||||
base::win::PEImage pe(&__ImageBase);
|
||||
PIMAGE_NT_HEADERS headers = pe.GetNTHeaders();
|
||||
CHECK(headers);
|
||||
module.size = headers->OptionalHeader.SizeOfImage;
|
||||
module.timestamp = headers->FileHeader.TimeDateStamp;
|
||||
|
||||
GUID guid;
|
||||
DWORD age;
|
||||
LPCSTR pdb_filename = nullptr;
|
||||
size_t pdb_filename_length = 0;
|
||||
if (pe.GetDebugId(&guid, &age, &pdb_filename, &pdb_filename_length)) {
|
||||
module.age = age;
|
||||
static_assert(sizeof(module.identifier) >= sizeof(guid),
|
||||
"Identifier field must be able to contain a GUID.");
|
||||
memcpy(module.identifier, &guid, sizeof(guid));
|
||||
} else {
|
||||
memset(module.identifier, 0, sizeof(module.identifier));
|
||||
}
|
||||
|
||||
module.file = "chrome.dll";
|
||||
module.debug_file =
|
||||
std::string(base::StringPiece(pdb_filename, pdb_filename_length));
|
||||
|
||||
global_tracker->RecordModuleInfo(module);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ExtendedCrashReporting::ExtendedCrashReporting(
|
||||
base::debug::GlobalActivityTracker* tracker)
|
||||
: tracker_(tracker) {}
|
||||
|
||||
ExtendedCrashReporting::~ExtendedCrashReporting() {
|
||||
if (veh_handle_)
|
||||
::RemoveVectoredExceptionHandler(veh_handle_);
|
||||
}
|
||||
|
||||
ExtendedCrashReporting* ExtendedCrashReporting::SetUpIfEnabled(
|
||||
ProcessType process_type) {
|
||||
DCHECK_EQ(nullptr, g_instance);
|
||||
if (!base::FeatureList::IsEnabled(kExtendedCrashReportingFeature)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return SetUpImpl(process_type);
|
||||
}
|
||||
|
||||
ExtendedCrashReporting* ExtendedCrashReporting::GetInstance() {
|
||||
return g_instance;
|
||||
}
|
||||
|
||||
void ExtendedCrashReporting::SetProductStrings(
|
||||
const std::u16string& product_name,
|
||||
const std::u16string& product_version,
|
||||
const std::u16string& channel_name,
|
||||
const std::u16string& special_build) {
|
||||
base::debug::ActivityUserData& proc_data = tracker_->process_data();
|
||||
proc_data.SetString(kActivityProduct, product_name);
|
||||
proc_data.SetString(kActivityVersion, product_version);
|
||||
proc_data.SetString(kActivityChannel, channel_name);
|
||||
proc_data.SetString(kActivitySpecialBuild, special_build);
|
||||
}
|
||||
|
||||
void ExtendedCrashReporting::SetBool(base::StringPiece name, bool value) {
|
||||
tracker_->process_data().SetBool(name, value);
|
||||
}
|
||||
|
||||
void ExtendedCrashReporting::SetInt(base::StringPiece name, int64_t value) {
|
||||
tracker_->process_data().SetInt(name, value);
|
||||
}
|
||||
|
||||
void ExtendedCrashReporting::SetDataBool(base::StringPiece name, bool value) {
|
||||
if (g_instance)
|
||||
g_instance->SetBool(name, value);
|
||||
}
|
||||
|
||||
void ExtendedCrashReporting::SetDataInt(base::StringPiece name, int64_t value) {
|
||||
if (g_instance)
|
||||
g_instance->SetInt(name, value);
|
||||
}
|
||||
|
||||
void ExtendedCrashReporting::RegisterVEH() {
|
||||
#if defined(ADDRESS_SANITIZER)
|
||||
// ASAN on windows x64 is dynamically allocating the shadow memory on a
|
||||
// memory access violation by setting up an vector exception handler.
|
||||
// When instrumented with ASAN, this code may trigger an exception by
|
||||
// accessing unallocated shadow memory, which is causing an infinite
|
||||
// recursion (i.e. infinite memory access violation).
|
||||
(void)&VectoredExceptionHandler;
|
||||
#else
|
||||
DCHECK_EQ(nullptr, veh_handle_);
|
||||
// Register a vectored exception handler and request it be first. Note that
|
||||
// subsequent registrations may also request to be first, in which case this
|
||||
// one will be bumped.
|
||||
// TODO(manzagop): Depending on observations, it may be necessary to
|
||||
// consider refreshing the registration, either periodically or at opportune
|
||||
// (e.g. risky) times.
|
||||
veh_handle_ = ::AddVectoredExceptionHandler(1, &VectoredExceptionHandler);
|
||||
DCHECK(veh_handle_);
|
||||
#endif // ADDRESS_SANITIZER
|
||||
}
|
||||
|
||||
void ExtendedCrashReporting::SetUpForTesting() {
|
||||
ExtendedCrashReporting::SetUpImpl(kBrowserProcess);
|
||||
}
|
||||
|
||||
void ExtendedCrashReporting::TearDownForTesting() {
|
||||
if (g_instance) {
|
||||
ExtendedCrashReporting* instance_to_delete = g_instance;
|
||||
g_instance = nullptr;
|
||||
delete instance_to_delete;
|
||||
}
|
||||
|
||||
// Clear the crash annotation.
|
||||
ActivityTrackerAnnotation::GetInstance()->Clear();
|
||||
}
|
||||
|
||||
ExtendedCrashReporting* ExtendedCrashReporting::SetUpImpl(
|
||||
ProcessType process_type) {
|
||||
DCHECK_EQ(nullptr, g_instance);
|
||||
|
||||
// TODO(https://crbug.com/1044707): Adjust these numbers once there is real
|
||||
// data to show just how much of an arena is necessary.
|
||||
const size_t kMemorySize = 1 << 20; // 1 MiB
|
||||
const int kStackDepth = 4;
|
||||
const uint64_t kAllocatorId = 0;
|
||||
|
||||
base::debug::GlobalActivityTracker::CreateWithAllocator(
|
||||
std::make_unique<base::LocalPersistentMemoryAllocator>(
|
||||
kMemorySize, kAllocatorId, kExtendedCrashReportingFeature.name),
|
||||
kStackDepth, 0);
|
||||
|
||||
// Track code activities (such as posting task, blocking on locks, and
|
||||
// joining threads) that can cause hanging threads and general instability
|
||||
base::debug::GlobalActivityTracker* global_tracker =
|
||||
base::debug::GlobalActivityTracker::Get();
|
||||
DCHECK(global_tracker);
|
||||
|
||||
// Construct the instance with the new global tracker, this object is
|
||||
// intentionally leaked.
|
||||
std::unique_ptr<ExtendedCrashReporting> new_instance =
|
||||
base::WrapUnique(new ExtendedCrashReporting(global_tracker));
|
||||
new_instance->Initialize(process_type);
|
||||
g_instance = new_instance.release();
|
||||
return g_instance;
|
||||
}
|
||||
|
||||
void ExtendedCrashReporting::Initialize(ProcessType process_type) {
|
||||
// Record the location and size of the tracker memory range in a Crashpad
|
||||
// annotation to allow the handler to retrieve it on crash.
|
||||
// Record the buffer size and location for the annotation beacon.
|
||||
auto* allocator = tracker_->allocator();
|
||||
ActivityTrackerAnnotation::GetInstance()->SetValue(allocator->data(),
|
||||
allocator->size());
|
||||
|
||||
// Record the main DLL module info for easier symbolization.
|
||||
RecordChromeModuleInfo(tracker_);
|
||||
|
||||
LogActivityRecordEvent(ActivityRecordEvent::kGotTracker);
|
||||
|
||||
base::debug::ActivityUserData& proc_data = tracker_->process_data();
|
||||
#if defined(ARCH_CPU_X86)
|
||||
proc_data.SetString(kActivityPlatform, "Win32");
|
||||
#elif defined(ARCH_CPU_X86_64)
|
||||
proc_data.SetString(kActivityPlatform, "Win64");
|
||||
#endif
|
||||
proc_data.SetInt(
|
||||
kActivityStartTimestamp,
|
||||
base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds());
|
||||
|
||||
if (process_type == kBrowserProcess)
|
||||
proc_data.SetInt(kActivityProcessType, ProcessState::BROWSER_PROCESS);
|
||||
|
||||
RegisterVEH();
|
||||
}
|
||||
|
||||
} // namespace browser_watcher
|
@ -1,71 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef COMPONENTS_BROWSER_WATCHER_EXTENDED_CRASH_REPORTING_H_
|
||||
#define COMPONENTS_BROWSER_WATCHER_EXTENDED_CRASH_REPORTING_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/strings/string_piece.h"
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
class GlobalActivityTracker;
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
|
||||
namespace browser_watcher {
|
||||
|
||||
class ExtendedCrashReporting {
|
||||
public:
|
||||
enum ProcessType { kBrowserProcess, kOther };
|
||||
|
||||
~ExtendedCrashReporting();
|
||||
|
||||
// Initializes extended crash reporting for this process if enabled.
|
||||
// Returns nullptr if extended crash reporting is disabled.
|
||||
// Should only be called once in any one process.
|
||||
static ExtendedCrashReporting* SetUpIfEnabled(ProcessType process_type);
|
||||
|
||||
// Retrieves the extended crash reporting instance for this process if
|
||||
// it exists, or nullptr if it does not.
|
||||
static ExtendedCrashReporting* GetInstance();
|
||||
|
||||
// Records identifying strings for the product and version for an extended
|
||||
// crash report. This function is threadsafe.
|
||||
void SetProductStrings(const std::u16string& product_name,
|
||||
const std::u16string& product_version,
|
||||
const std::u16string& channel_name,
|
||||
const std::u16string& special_build);
|
||||
|
||||
// Adds or updates the global extended crash reporting data.
|
||||
// These functions are threadsafe.
|
||||
void SetBool(base::StringPiece name, bool value);
|
||||
void SetInt(base::StringPiece name, int64_t value);
|
||||
|
||||
// Adds or updates the global extended crash reporting data, if enabled.
|
||||
static void SetDataBool(base::StringPiece name, bool value);
|
||||
static void SetDataInt(base::StringPiece name, int64_t value);
|
||||
|
||||
// Allows tests to initialize and teardown the global instance.
|
||||
static void SetUpForTesting();
|
||||
static void TearDownForTesting();
|
||||
|
||||
private:
|
||||
explicit ExtendedCrashReporting(base::debug::GlobalActivityTracker* tracker);
|
||||
static ExtendedCrashReporting* SetUpImpl(ProcessType process_type);
|
||||
void Initialize(ProcessType process_type);
|
||||
|
||||
// Registers a vectored exception handler that stores exception details to the
|
||||
// activity report on exception - handled or not.
|
||||
void RegisterVEH();
|
||||
|
||||
raw_ptr<void> veh_handle_ = nullptr;
|
||||
const raw_ptr<base::debug::GlobalActivityTracker> tracker_;
|
||||
};
|
||||
|
||||
} // namespace browser_watcher
|
||||
|
||||
#endif // COMPONENTS_BROWSER_WATCHER_EXTENDED_CRASH_REPORTING_H_
|
@ -1,21 +0,0 @@
|
||||
// Copyright 2017 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "components/browser_watcher/extended_crash_reporting_metrics.h"
|
||||
|
||||
#include "base/metrics/histogram_functions.h"
|
||||
|
||||
namespace browser_watcher {
|
||||
|
||||
void LogCollectOnCrashEvent(CollectOnCrashEvent event) {
|
||||
base::UmaHistogramEnumeration("ActivityTracker.CollectCrash.Event", event,
|
||||
CollectOnCrashEvent::kCollectOnCrashEventMax);
|
||||
}
|
||||
|
||||
void LogActivityRecordEvent(ActivityRecordEvent event) {
|
||||
base::UmaHistogramEnumeration("ActivityTracker.Record.Event", event,
|
||||
ActivityRecordEvent::kActivityRecordEventMax);
|
||||
}
|
||||
|
||||
} // namespace browser_watcher
|
@ -1,42 +0,0 @@
|
||||
// Copyright 2017 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef COMPONENTS_BROWSER_WATCHER_EXTENDED_CRASH_REPORTING_METRICS_H_
|
||||
#define COMPONENTS_BROWSER_WATCHER_EXTENDED_CRASH_REPORTING_METRICS_H_
|
||||
|
||||
namespace browser_watcher {
|
||||
|
||||
// DO NOT REMOVE OR REORDER VALUES. This is logged persistently in a histogram.
|
||||
enum class CollectOnCrashEvent {
|
||||
kCollectAttempt,
|
||||
kUserDataDirNotEmptyUnused, // No longer used.
|
||||
kPathExistsUnused, // No longer used.
|
||||
kReportExtractionSuccess,
|
||||
kPmaSetDeletedFailedUnused, // No longer used.
|
||||
kOpenForDeleteFailedUnused, // No longer used.
|
||||
kSuccess,
|
||||
kInMemoryAnnotationExists,
|
||||
// New values go here.
|
||||
kCollectOnCrashEventMax
|
||||
};
|
||||
|
||||
// DO NOT REMOVE OR REORDER VALUES. This is logged persistently in a histogram.
|
||||
enum class ActivityRecordEvent {
|
||||
kRecordAttempt,
|
||||
kActivityDirectoryExistsUnused, // No longer used.
|
||||
kGotActivityPathUnused, // No longer used.
|
||||
kGotTracker,
|
||||
kMarkDeletedUnused, // No longer used.
|
||||
kMarkDeletedGotFileUnused, // No longer used.
|
||||
kOpenForDeleteFailedUnused, // No longer used.
|
||||
// New values go here.
|
||||
kActivityRecordEventMax
|
||||
};
|
||||
|
||||
void LogCollectOnCrashEvent(CollectOnCrashEvent event);
|
||||
void LogActivityRecordEvent(ActivityRecordEvent event);
|
||||
|
||||
} // namespace browser_watcher
|
||||
|
||||
#endif // COMPONENTS_BROWSER_WATCHER_EXTENDED_CRASH_REPORTING_METRICS_H_
|
@ -1,165 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "components/browser_watcher/extended_crash_reporting.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "base/debug/activity_analyzer.h"
|
||||
#include "base/debug/activity_tracker.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/files/scoped_temp_dir.h"
|
||||
#include "base/metrics/persistent_memory_allocator.h"
|
||||
#include "base/process/process.h"
|
||||
#include "base/test/multiprocess_test.h"
|
||||
#include "base/test/scoped_feature_list.h"
|
||||
#include "build/build_config.h"
|
||||
#include "components/browser_watcher/activity_report.pb.h"
|
||||
#include "components/browser_watcher/activity_report_extractor.h"
|
||||
#include "components/browser_watcher/activity_tracker_annotation.h"
|
||||
#include "components/browser_watcher/features.h"
|
||||
#include "components/crash/core/common/crash_key.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "testing/multiprocess_func_list.h"
|
||||
#include "third_party/crashpad/crashpad/client/annotation_list.h"
|
||||
|
||||
namespace browser_watcher {
|
||||
|
||||
namespace {
|
||||
|
||||
crashpad::Annotation* GetActivtyTrackerAnnotation() {
|
||||
crashpad::AnnotationList* annotation_list = crashpad::AnnotationList::Get();
|
||||
for (auto it = annotation_list->begin(); it != annotation_list->end(); ++it) {
|
||||
if (strcmp(ActivityTrackerAnnotation::kAnnotationName, (*it)->name()) ==
|
||||
0 &&
|
||||
ActivityTrackerAnnotation::kAnnotationType == (*it)->type()) {
|
||||
return *it;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool IsNullOrEmpty(crashpad::Annotation* annotation) {
|
||||
return annotation == nullptr || annotation->size() == 0;
|
||||
}
|
||||
|
||||
bool IsNonEmpty(crashpad::Annotation* annotation) {
|
||||
return annotation != nullptr && annotation->size() > 0;
|
||||
}
|
||||
|
||||
using base::debug::GlobalActivityAnalyzer;
|
||||
using base::debug::GlobalActivityTracker;
|
||||
|
||||
constexpr uint32_t kExceptionCode = 42U;
|
||||
constexpr uint32_t kExceptionFlagContinuable = 0U;
|
||||
|
||||
} // namespace
|
||||
|
||||
class ExtendedCrashReportingTest : public testing::Test {
|
||||
public:
|
||||
ExtendedCrashReportingTest() {}
|
||||
~ExtendedCrashReportingTest() override {
|
||||
GlobalActivityTracker* global_tracker = GlobalActivityTracker::Get();
|
||||
if (global_tracker) {
|
||||
global_tracker->ReleaseTrackerForCurrentThreadForTesting();
|
||||
delete global_tracker;
|
||||
}
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
testing::Test::SetUp();
|
||||
|
||||
// Initialize the crash keys, which will also reset the activity tracker
|
||||
// annotation if it's been used in previous tests.
|
||||
crash_reporter::InitializeCrashKeysForTesting();
|
||||
}
|
||||
void TearDown() override {
|
||||
ExtendedCrashReporting::TearDownForTesting();
|
||||
crash_reporter::ResetCrashKeysForTesting();
|
||||
testing::Test::TearDown();
|
||||
}
|
||||
|
||||
std::unique_ptr<GlobalActivityAnalyzer> CreateAnalyzer() {
|
||||
GlobalActivityTracker* tracker = GlobalActivityTracker::Get();
|
||||
EXPECT_TRUE(tracker);
|
||||
base::PersistentMemoryAllocator* tmp = tracker->allocator();
|
||||
|
||||
return GlobalActivityAnalyzer::CreateWithAllocator(
|
||||
std::make_unique<base::PersistentMemoryAllocator>(
|
||||
const_cast<void*>(tmp->data()), tmp->size(), 0u, 0u, "Copy", true));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ExtendedCrashReportingTest, DisabledByDefault) {
|
||||
EXPECT_EQ(nullptr, ExtendedCrashReporting::SetUpIfEnabled(
|
||||
ExtendedCrashReporting::kBrowserProcess));
|
||||
EXPECT_EQ(nullptr, ExtendedCrashReporting::GetInstance());
|
||||
}
|
||||
|
||||
TEST_F(ExtendedCrashReportingTest, SetUpIsEnabledByFeatureFlag) {
|
||||
base::test::ScopedFeatureList feature_list;
|
||||
feature_list.InitAndEnableFeature(kExtendedCrashReportingFeature);
|
||||
|
||||
ExtendedCrashReporting* reporting = ExtendedCrashReporting::SetUpIfEnabled(
|
||||
ExtendedCrashReporting::kBrowserProcess);
|
||||
EXPECT_NE(nullptr, reporting);
|
||||
EXPECT_EQ(reporting, ExtendedCrashReporting::GetInstance());
|
||||
}
|
||||
|
||||
TEST_F(ExtendedCrashReportingTest, RecordsAnnotation) {
|
||||
// Make sure the annotation doesn't exist before initialization.
|
||||
crashpad::Annotation* annotation = GetActivtyTrackerAnnotation();
|
||||
EXPECT_TRUE(IsNullOrEmpty(annotation));
|
||||
|
||||
ExtendedCrashReporting::SetUpForTesting();
|
||||
EXPECT_NE(nullptr, ExtendedCrashReporting::GetInstance());
|
||||
EXPECT_TRUE(IsNonEmpty(GetActivtyTrackerAnnotation()));
|
||||
}
|
||||
|
||||
#if defined(ADDRESS_SANITIZER) && BUILDFLAG(IS_WIN)
|
||||
// The test does not pass under WinASan. See crbug.com/809524.
|
||||
#define MAYBE_CrashingTest DISABLED_CrashingTest
|
||||
#else
|
||||
#define MAYBE_CrashingTest CrashingTest
|
||||
#endif
|
||||
TEST_F(ExtendedCrashReportingTest, MAYBE_CrashingTest) {
|
||||
ExtendedCrashReporting::SetUpForTesting();
|
||||
ExtendedCrashReporting* extended_crash_reporting =
|
||||
ExtendedCrashReporting::GetInstance();
|
||||
ASSERT_NE(nullptr, extended_crash_reporting);
|
||||
|
||||
// Raise an exception, then continue.
|
||||
__try {
|
||||
::RaiseException(kExceptionCode, kExceptionFlagContinuable, 0U, nullptr);
|
||||
} __except (EXCEPTION_CONTINUE_EXECUTION) {
|
||||
}
|
||||
|
||||
// Collect the report.
|
||||
StabilityReport report;
|
||||
ASSERT_EQ(SUCCESS, Extract(CreateAnalyzer(), &report));
|
||||
|
||||
// Validate expectations.
|
||||
ASSERT_EQ(1, report.process_states_size());
|
||||
const ProcessState& process_state = report.process_states(0);
|
||||
ASSERT_EQ(1, process_state.threads_size());
|
||||
|
||||
bool thread_found = false;
|
||||
for (const ThreadState& thread : process_state.threads()) {
|
||||
if (thread.thread_id() == ::GetCurrentThreadId()) {
|
||||
thread_found = true;
|
||||
ASSERT_TRUE(thread.has_exception());
|
||||
const Exception& exception = thread.exception();
|
||||
EXPECT_EQ(kExceptionCode, exception.code());
|
||||
EXPECT_NE(0ULL, exception.program_counter());
|
||||
EXPECT_NE(0ULL, exception.exception_address());
|
||||
EXPECT_NE(0LL, exception.time());
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(thread_found);
|
||||
}
|
||||
|
||||
} // namespace browser_watcher
|
@ -1,15 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "components/browser_watcher/features.h"
|
||||
|
||||
namespace browser_watcher {
|
||||
|
||||
BASE_FEATURE(kExtendedCrashReportingFeature,
|
||||
"ExtendedCrashReporting",
|
||||
base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
|
||||
const char kInMemoryOnlyParam[] = "in_memory_only";
|
||||
|
||||
} // namespace browser_watcher
|
@ -1,22 +0,0 @@
|
||||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef COMPONENTS_BROWSER_WATCHER_FEATURES_H_
|
||||
#define COMPONENTS_BROWSER_WATCHER_FEATURES_H_
|
||||
|
||||
#include "base/feature_list.h"
|
||||
|
||||
namespace browser_watcher {
|
||||
|
||||
// Enables activity tracking and extending crash reports with structured
|
||||
// high-level program state.
|
||||
BASE_DECLARE_FEATURE(kExtendedCrashReportingFeature);
|
||||
|
||||
// Name of an experiment parameter that controls whether to record browser
|
||||
// activity in-memory only.
|
||||
extern const char kInMemoryOnlyParam[];
|
||||
|
||||
} // namespace browser_watcher
|
||||
|
||||
#endif // COMPONENTS_BROWSER_WATCHER_FEATURES_H_
|
@ -1,47 +0,0 @@
|
||||
// Copyright 2017 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// A utility for testing locally the retrieval of system session events.
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/time/time.h"
|
||||
#include "components/metrics/system_session_analyzer/system_session_analyzer_win.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using metrics::SystemSessionAnalyzer;
|
||||
|
||||
class SystemSessionEventFetcher : public SystemSessionAnalyzer {
|
||||
public:
|
||||
explicit SystemSessionEventFetcher() : SystemSessionAnalyzer(0) {}
|
||||
using SystemSessionAnalyzer::FetchEvents;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
SystemSessionEventFetcher fetcher;
|
||||
std::vector<SystemSessionEventFetcher::EventInfo> events;
|
||||
// Retrieve events for the last 5 sessions. We expect our own sessions start
|
||||
// event, and then 2 events per each preceding session for 11 total.
|
||||
if (!fetcher.FetchEvents(11U, &events)) {
|
||||
std::cerr << "Failed to fetch events." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Print the event ids and times.
|
||||
for (const SystemSessionEventFetcher::EventInfo& event : events) {
|
||||
base::Time::Exploded exploded = {};
|
||||
event.event_time.LocalExplode(&exploded);
|
||||
std::string time = base::StringPrintf(
|
||||
"%d/%d/%d %d:%02d:%02d", exploded.month, exploded.day_of_month,
|
||||
exploded.year, exploded.hour, exploded.minute, exploded.second);
|
||||
std::cout << "Event: " << event.event_id << " (" << time << ")"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
// Copyright 2017 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef COMPONENTS_BROWSER_WATCHER_MINIDUMP_USER_STREAMS_H_
|
||||
#define COMPONENTS_BROWSER_WATCHER_MINIDUMP_USER_STREAMS_H_
|
||||
|
||||
namespace browser_watcher {
|
||||
|
||||
// The stream type assigned to the minidump stream that holds the serialized
|
||||
// stability report.
|
||||
// Note: the value was obtained by adding 1 to the stream type used for holding
|
||||
// the SyzyAsan proto.
|
||||
constexpr uint32_t kActivityReportStreamType = 0x4B6B0002;
|
||||
|
||||
} // namespace browser_watcher
|
||||
|
||||
#endif // COMPONENTS_BROWSER_WATCHER_MINIDUMP_USER_STREAMS_H_
|
@ -16,10 +16,6 @@ source_set("keep_alive_registry") {
|
||||
]
|
||||
|
||||
deps = [ "//base" ]
|
||||
|
||||
if (is_win) {
|
||||
deps += [ "//components/browser_watcher:stability_client" ]
|
||||
}
|
||||
}
|
||||
|
||||
source_set("unit_tests") {
|
||||
|
@ -1,3 +0,0 @@
|
||||
include_rules = [
|
||||
"+components/browser_watcher",
|
||||
]
|
@ -6,15 +6,9 @@
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/observer_list.h"
|
||||
#include "build/build_config.h"
|
||||
#include "components/keep_alive_registry/keep_alive_state_observer.h"
|
||||
#include "components/keep_alive_registry/keep_alive_types.h"
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
#include "components/browser_watcher/activity_data_names.h"
|
||||
#include "components/browser_watcher/extended_crash_reporting.h"
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Public methods
|
||||
|
||||
@ -174,10 +168,6 @@ void KeepAliveRegistry::Unregister(KeepAliveOrigin origin,
|
||||
void KeepAliveRegistry::OnKeepAliveStateChanged(bool new_keeping_alive) {
|
||||
DVLOG(1) << "Notifying KeepAliveStateObservers: KeepingAlive changed to: "
|
||||
<< new_keeping_alive;
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
browser_watcher::ExtendedCrashReporting::SetDataBool(
|
||||
browser_watcher::kActivityKeepAlive, new_keeping_alive);
|
||||
#endif
|
||||
for (KeepAliveStateObserver& observer : observers_)
|
||||
observer.OnKeepAliveStateChanged(new_keeping_alive);
|
||||
}
|
||||
@ -185,10 +175,6 @@ void KeepAliveRegistry::OnKeepAliveStateChanged(bool new_keeping_alive) {
|
||||
void KeepAliveRegistry::OnRestartAllowedChanged(bool new_restart_allowed) {
|
||||
DVLOG(1) << "Notifying KeepAliveStateObservers: Restart changed to: "
|
||||
<< new_restart_allowed;
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
browser_watcher::ExtendedCrashReporting::SetDataBool(
|
||||
browser_watcher::kActivityRestartAllowed, new_restart_allowed);
|
||||
#endif
|
||||
for (KeepAliveStateObserver& observer : observers_)
|
||||
observer.OnKeepAliveRestartStateChanged(new_restart_allowed);
|
||||
}
|
||||
|
@ -236,7 +236,6 @@ static_library("metrics") {
|
||||
"system_session_analyzer/system_session_analyzer_win.cc",
|
||||
"system_session_analyzer/system_session_analyzer_win.h",
|
||||
]
|
||||
deps += [ "//components/browser_watcher:stability_client" ]
|
||||
libs = [ "wevtapi.lib" ]
|
||||
} else {
|
||||
sources += [ "machine_id_provider_nonwin.cc" ]
|
||||
|
@ -2,7 +2,6 @@
|
||||
# dependencies to a minimal set.
|
||||
include_rules = [
|
||||
"-components",
|
||||
"+components/browser_watcher",
|
||||
"+components/component_updater",
|
||||
"+components/flags_ui",
|
||||
"+components/metrics",
|
||||
|
@ -158,7 +158,6 @@ if (current_cpu == "x86") {
|
||||
"//chrome/common:constants",
|
||||
"//chrome/install_static:install_static_util",
|
||||
"//chrome/installer/util:with_no_strings",
|
||||
"//components/browser_watcher:browser_watcher_client",
|
||||
"//components/crash/core/common",
|
||||
"//components/flags_ui:switches",
|
||||
"//components/policy:generated",
|
||||
|
Reference in New Issue
Block a user