blob: e0110ca9583cabead5aa27ac4073e4b1c6d99a83 [file] [log] [blame]
// 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 "components/stability_report/user_stream_data_source.h"
#include <windows.h>
// Must be included after windows.h.
#include <psapi.h>
#include <memory>
#include <string>
#include <utility>
#include "base/check.h"
#include "base/dcheck_is_on.h"
#include "base/numerics/safe_conversions.h"
#include "base/process/memory.h"
#include "base/process/process.h"
#include "components/stability_report/stability_report.pb.h"
#include "third_party/crashpad/crashpad/minidump/minidump_user_extension_stream_data_source.h"
#include "third_party/crashpad/crashpad/snapshot/exception_snapshot.h"
#include "third_party/crashpad/crashpad/snapshot/process_snapshot.h"
namespace stability_report {
namespace {
// 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 kStreamType = 0x4B6B0002;
// System memory metrics are reported in pages. Use this page size to scale
// process memory metrics to the same units.
constexpr size_t kPageSize = 4096;
// A data source that holds a serialized StabilityReport.
class StabilityReportDataSource final
: public crashpad::MinidumpUserExtensionStreamDataSource {
public:
explicit StabilityReportDataSource(const StabilityReport& report);
~StabilityReportDataSource() final = default;
StabilityReportDataSource(const StabilityReportDataSource&) = delete;
StabilityReportDataSource& operator=(const StabilityReportDataSource&) =
delete;
size_t StreamDataSize() final { return data_.size(); }
bool ReadStreamData(Delegate* delegate) final;
private:
std::string data_;
};
StabilityReportDataSource::StabilityReportDataSource(
const StabilityReport& report)
: crashpad::MinidumpUserExtensionStreamDataSource(kStreamType),
data_(report.SerializeAsString()) {
// On error, SerializeAsString() will return an empty string which will
// cause ReadStreamData() to harmlessly return no data.
}
bool StabilityReportDataSource::ReadStreamData(Delegate* delegate) {
return delegate->ExtensionStreamDataSourceRead(data_.data(), data_.size());
}
// Adds system metrics to `report`.
void CollectSystemPerformanceMetrics(StabilityReport* report) {
// Grab system commit memory. Best effort.
PERFORMANCE_INFORMATION perf_info = {sizeof(perf_info)};
if (!::GetPerformanceInfo(&perf_info, sizeof(perf_info))) {
return;
}
SystemMemoryState::WindowsMemory* 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);
// The process memory metrics won't be scaled correctly with an unexpected
// page size.
DCHECK_EQ(perf_info.PageSize, kPageSize);
}
// Adds metrics for the process in `process_snapshot` to `report`.
void CollectProcessPerformanceMetrics(
const crashpad::ProcessSnapshot& process_snapshot,
StabilityReport* report) {
const base::ProcessId process_id = process_snapshot.ProcessID();
#if DCHECK_IS_ON()
// Ensure no ProcessState was created yet for the process in question.
for (const ProcessState& process_state : report->process_states()) {
DCHECK_NE(process_state.process_id(), process_id);
}
#endif
ProcessState* process_state = report->add_process_states();
process_state->set_process_id(process_id);
ProcessState::MemoryState::WindowsMemory* memory_state =
process_state->mutable_memory_state()->mutable_windows_memory();
// Grab the requested allocation size in case of OOM exception.
const crashpad::ExceptionSnapshot* const exception =
process_snapshot.Exception();
if (exception && exception->Exception() == base::win::kOomExceptionCode &&
!exception->Codes().empty()) {
// The first parameter, if present, is the size of the allocation attempt.
// Note Codes() contains 64-bit values but `process_allocation_attempt` is
// a uint32.
memory_state->set_process_allocation_attempt(
base::saturated_cast<uint32_t>(exception->Codes().front()));
}
const base::Process process = base::Process::OpenWithAccess(
process_id, PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ);
if (!process.IsValid()) {
return;
}
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.
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);
}
}
} // namespace
std::unique_ptr<crashpad::MinidumpUserExtensionStreamDataSource>
UserStreamDataSource::ProduceStreamData(
crashpad::ProcessSnapshot* process_snapshot) {
DCHECK(process_snapshot);
StabilityReport report;
CollectSystemPerformanceMetrics(&report);
CollectProcessPerformanceMetrics(*process_snapshot, &report);
return std::make_unique<StabilityReportDataSource>(report);
}
} // namespace stability_report