blob: d7ccb60dde805642e471dc57827f6f5663024a0a [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/task_manager/sampling/task_group.h"
#include <algorithm>
#include <limits>
#include "base/bind.h"
#include "base/callback.h"
#include "base/stl_util.h"
#include "base/task/post_task.h"
#include "build/build_config.h"
#include "chrome/browser/task_manager/sampling/shared_sampler.h"
#include "chrome/browser/task_manager/task_manager_observer.h"
#include "components/nacl/browser/nacl_browser.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "gpu/ipc/common/memory_stats.h"
#if defined(OS_WIN)
#include <windows.h>
#endif
namespace task_manager {
namespace {
// A mask for the refresh types that are done in the background thread.
const int kBackgroundRefreshTypesMask =
REFRESH_TYPE_CPU | REFRESH_TYPE_SWAPPED_MEM | REFRESH_TYPE_IDLE_WAKEUPS |
#if defined(OS_WIN)
REFRESH_TYPE_START_TIME | REFRESH_TYPE_CPU_TIME |
#endif // defined(OS_WIN)
#if defined(OS_LINUX) || defined(OS_MACOSX)
REFRESH_TYPE_FD_COUNT |
#endif // defined(OS_LINUX) || defined(OS_MACOSX)
#if BUILDFLAG(ENABLE_NACL)
REFRESH_TYPE_NACL |
#endif // BUILDFLAG(ENABLE_NACL)
REFRESH_TYPE_PRIORITY;
#if defined(OS_WIN)
// Gets the GDI and USER Handles on Windows at one shot.
void GetWindowsHandles(base::ProcessHandle handle,
int64_t* out_gdi_current,
int64_t* out_gdi_peak,
int64_t* out_user_current,
int64_t* out_user_peak) {
*out_gdi_current = 0;
*out_gdi_peak = 0;
*out_user_current = 0;
*out_user_peak = 0;
// Get a handle to |process| that has PROCESS_QUERY_INFORMATION rights.
HANDLE current_process = GetCurrentProcess();
HANDLE process_with_query_rights;
if (DuplicateHandle(current_process, handle, current_process,
&process_with_query_rights, PROCESS_QUERY_INFORMATION,
false, 0)) {
*out_gdi_current = static_cast<int64_t>(
GetGuiResources(process_with_query_rights, GR_GDIOBJECTS));
*out_gdi_peak = static_cast<int64_t>(
GetGuiResources(process_with_query_rights, GR_GDIOBJECTS_PEAK));
*out_user_current = static_cast<int64_t>(
GetGuiResources(process_with_query_rights, GR_USEROBJECTS));
*out_user_peak = static_cast<int64_t>(
GetGuiResources(process_with_query_rights, GR_USEROBJECTS_PEAK));
CloseHandle(process_with_query_rights);
}
}
#endif // defined(OS_WIN)
#if BUILDFLAG(ENABLE_NACL)
int GetNaClDebugStubPortOnIoThread(int process_id) {
return nacl::NaClBrowser::GetInstance()->GetProcessGdbDebugStubPort(
process_id);
}
#endif // BUILDFLAG(ENABLE_NACL)
} // namespace
TaskGroup::TaskGroup(
base::ProcessHandle proc_handle,
base::ProcessId proc_id,
bool is_running_in_vm,
const base::Closure& on_background_calculations_done,
const scoped_refptr<SharedSampler>& shared_sampler,
const scoped_refptr<base::SequencedTaskRunner>& blocking_pool_runner)
: process_handle_(proc_handle),
process_id_(proc_id),
is_running_in_vm_(is_running_in_vm),
on_background_calculations_done_(on_background_calculations_done),
worker_thread_sampler_(nullptr),
shared_sampler_(shared_sampler),
#if defined(OS_CHROMEOS)
arc_shared_sampler_(nullptr),
#endif // defined(OS_CHROMEOS)
expected_on_bg_done_flags_(kBackgroundRefreshTypesMask),
current_on_bg_done_flags_(0),
platform_independent_cpu_usage_(std::numeric_limits<double>::quiet_NaN()),
swapped_mem_bytes_(-1),
memory_footprint_(-1),
gpu_memory_(-1),
per_process_network_usage_rate_(-1),
cumulative_per_process_network_usage_(0),
#if defined(OS_WIN)
gdi_current_handles_(-1),
gdi_peak_handles_(-1),
user_current_handles_(-1),
user_peak_handles_(-1),
hard_faults_per_second_(-1),
#endif // defined(OS_WIN)
#if BUILDFLAG(ENABLE_NACL)
nacl_debug_stub_port_(nacl::kGdbDebugStubPortUnknown),
#endif // BUILDFLAG(ENABLE_NACL)
#if defined(OS_LINUX) || defined(OS_MACOSX)
open_fd_count_(-1),
#endif // defined(OS_LINUX) || defined(OS_MACOSX)
idle_wakeups_per_second_(-1),
gpu_memory_has_duplicates_(false),
is_backgrounded_(false) {
if (process_id_ != base::kNullProcessId && !is_running_in_vm_) {
worker_thread_sampler_ = base::MakeRefCounted<TaskGroupSampler>(
base::Process::Open(process_id_), blocking_pool_runner,
base::Bind(&TaskGroup::OnCpuRefreshDone,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&TaskGroup::OnSwappedMemRefreshDone,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&TaskGroup::OnIdleWakeupsRefreshDone,
weak_ptr_factory_.GetWeakPtr()),
#if defined(OS_LINUX) || defined(OS_MACOSX)
base::Bind(&TaskGroup::OnOpenFdCountRefreshDone,
weak_ptr_factory_.GetWeakPtr()),
#endif // defined(OS_LINUX) || defined(OS_MACOSX)
base::Bind(&TaskGroup::OnProcessPriorityDone,
weak_ptr_factory_.GetWeakPtr()));
shared_sampler_->RegisterCallback(
process_id_,
base::Bind(&TaskGroup::OnSamplerRefreshDone, base::Unretained(this)));
}
}
TaskGroup::~TaskGroup() {
shared_sampler_->UnregisterCallback(process_id_);
#if defined(OS_CHROMEOS)
if (arc_shared_sampler_)
arc_shared_sampler_->UnregisterCallback(process_id_);
#endif // defined(OS_CHROMEOS)
}
void TaskGroup::AddTask(Task* task) {
DCHECK(task);
tasks_.push_back(task);
}
void TaskGroup::RemoveTask(Task* task) {
DCHECK(task);
base::Erase(tasks_, task);
}
void TaskGroup::Refresh(const gpu::VideoMemoryUsageStats& gpu_memory_stats,
base::TimeDelta update_interval,
int64_t refresh_flags) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!empty());
if (is_running_in_vm_)
refresh_flags &= ~kUnsupportedVMRefreshFlags;
expected_on_bg_done_flags_ = refresh_flags & kBackgroundRefreshTypesMask;
// If a refresh type was recently disabled, we need to account for that too.
current_on_bg_done_flags_ &= expected_on_bg_done_flags_;
// First refresh the enabled non-expensive resources usages on the UI thread.
// 1- Refresh all the tasks as well as the total network usage (if enabled).
const bool network_usage_refresh_enabled =
TaskManagerObserver::IsResourceRefreshEnabled(REFRESH_TYPE_NETWORK_USAGE,
refresh_flags);
per_process_network_usage_rate_ = network_usage_refresh_enabled ? 0 : -1;
cumulative_per_process_network_usage_ = 0;
for (Task* task : tasks_) {
task->Refresh(update_interval, refresh_flags);
if (network_usage_refresh_enabled) {
per_process_network_usage_rate_ += task->network_usage_rate();
cumulative_per_process_network_usage_ += task->cumulative_network_usage();
}
}
// 2- Refresh GPU memory (if enabled).
if (TaskManagerObserver::IsResourceRefreshEnabled(REFRESH_TYPE_GPU_MEMORY,
refresh_flags)) {
RefreshGpuMemory(gpu_memory_stats);
}
// 3- Refresh Windows handles (if enabled).
#if defined(OS_WIN)
if (TaskManagerObserver::IsResourceRefreshEnabled(REFRESH_TYPE_HANDLES,
refresh_flags)) {
RefreshWindowsHandles();
}
#endif // defined(OS_WIN)
// 4- Refresh the NACL debug stub port (if enabled). This calls out to
// NaClBrowser on the browser's IO thread, completing asynchronously.
#if BUILDFLAG(ENABLE_NACL)
if (TaskManagerObserver::IsResourceRefreshEnabled(REFRESH_TYPE_NACL,
refresh_flags)) {
RefreshNaClDebugStubPort(tasks_[0]->GetChildProcessUniqueID());
}
#endif // BUILDFLAG(ENABLE_NACL)
int64_t shared_refresh_flags =
refresh_flags & shared_sampler_->GetSupportedFlags();
// 5- Refresh resources via SharedSampler if the current platform
// implementation supports that. The actual work is done on the worker thread.
// At the moment this is supported only on OS_WIN.
if (shared_refresh_flags != 0) {
shared_sampler_->Refresh(process_id_, shared_refresh_flags);
refresh_flags &= ~shared_refresh_flags;
}
// The remaining resource refreshes are time consuming and cannot be done on
// the UI thread. Do them all on the worker thread using the TaskGroupSampler.
// 6- CPU usage.
// 7- Memory usage.
// 8- Idle Wakeups per second.
// 9- (Linux and ChromeOS only) The number of file descriptors current open.
// 10- Process priority (foreground vs. background).
if (worker_thread_sampler_)
worker_thread_sampler_->Refresh(refresh_flags);
}
Task* TaskGroup::GetTaskById(TaskId task_id) const {
for (Task* task : tasks_) {
if (task->task_id() == task_id)
return task;
}
NOTREACHED();
return nullptr;
}
void TaskGroup::ClearCurrentBackgroundCalculationsFlags() {
current_on_bg_done_flags_ = 0;
}
bool TaskGroup::AreBackgroundCalculationsDone() const {
return expected_on_bg_done_flags_ == current_on_bg_done_flags_;
}
#if defined(OS_CHROMEOS)
void TaskGroup::SetArcSampler(ArcSharedSampler* sampler) {
DCHECK(sampler);
arc_shared_sampler_ = sampler;
arc_shared_sampler_->RegisterCallback(
process_id_, base::BindRepeating(&TaskGroup::OnArcSamplerRefreshDone,
weak_ptr_factory_.GetWeakPtr()));
}
#endif // defined(OS_CHROMEOS)
void TaskGroup::RefreshGpuMemory(
const gpu::VideoMemoryUsageStats& gpu_memory_stats) {
auto itr = gpu_memory_stats.process_map.find(process_id_);
if (itr == gpu_memory_stats.process_map.end()) {
gpu_memory_ = -1;
gpu_memory_has_duplicates_ = false;
return;
}
gpu_memory_ = itr->second.video_memory;
gpu_memory_has_duplicates_ = itr->second.has_duplicates;
}
void TaskGroup::RefreshWindowsHandles() {
#if defined(OS_WIN)
GetWindowsHandles(process_handle_,
&gdi_current_handles_,
&gdi_peak_handles_,
&user_current_handles_,
&user_peak_handles_);
#endif // defined(OS_WIN)
}
#if BUILDFLAG(ENABLE_NACL)
void TaskGroup::RefreshNaClDebugStubPort(int child_process_unique_id) {
base::PostTaskAndReplyWithResult(
FROM_HERE, {content::BrowserThread::IO},
base::Bind(&GetNaClDebugStubPortOnIoThread, child_process_unique_id),
base::Bind(&TaskGroup::OnRefreshNaClDebugStubPortDone,
weak_ptr_factory_.GetWeakPtr()));
}
void TaskGroup::OnRefreshNaClDebugStubPortDone(int nacl_debug_stub_port) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
nacl_debug_stub_port_ = nacl_debug_stub_port;
OnBackgroundRefreshTypeFinished(REFRESH_TYPE_NACL);
}
#endif // BUILDFLAG(ENABLE_NACL)
#if defined(OS_LINUX) || defined(OS_MACOSX)
void TaskGroup::OnOpenFdCountRefreshDone(int open_fd_count) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
open_fd_count_ = open_fd_count;
OnBackgroundRefreshTypeFinished(REFRESH_TYPE_FD_COUNT);
}
#endif // defined(OS_LINUX) || defined(OS_MACOSX)
void TaskGroup::OnCpuRefreshDone(double cpu_usage) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
platform_independent_cpu_usage_ = cpu_usage;
OnBackgroundRefreshTypeFinished(REFRESH_TYPE_CPU);
}
void TaskGroup::OnSwappedMemRefreshDone(int64_t swapped_mem_bytes) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
swapped_mem_bytes_ = swapped_mem_bytes;
OnBackgroundRefreshTypeFinished(REFRESH_TYPE_SWAPPED_MEM);
}
void TaskGroup::OnProcessPriorityDone(bool is_backgrounded) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
is_backgrounded_ = is_backgrounded;
OnBackgroundRefreshTypeFinished(REFRESH_TYPE_PRIORITY);
}
void TaskGroup::OnIdleWakeupsRefreshDone(int idle_wakeups_per_second) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
idle_wakeups_per_second_ = idle_wakeups_per_second;
OnBackgroundRefreshTypeFinished(REFRESH_TYPE_IDLE_WAKEUPS);
}
void TaskGroup::OnSamplerRefreshDone(
base::Optional<SharedSampler::SamplingResult> results) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// If any of the Optional<> fields have no value then replace them with
// sentinel values.
// TODO(wez): Migrate the TaskGroup fields to Optional<> so we can remove
// the need for all this sentinel-handling logic.
if (results) {
cpu_time_ = results->cpu_time;
idle_wakeups_per_second_ = results->idle_wakeups_per_second;
#if defined(OS_WIN)
hard_faults_per_second_ = results->hard_faults_per_second;
#endif
start_time_ = results->start_time;
} else {
cpu_time_ = base::TimeDelta();
idle_wakeups_per_second_ = -1;
#if defined(OS_WIN)
hard_faults_per_second_ = 0;
#endif
start_time_ = base::Time();
}
OnBackgroundRefreshTypeFinished(expected_on_bg_done_flags_ &
shared_sampler_->GetSupportedFlags());
}
#if defined(OS_CHROMEOS)
void TaskGroup::OnArcSamplerRefreshDone(
base::Optional<ArcSharedSampler::MemoryFootprintBytes> memory_footprint) {
if (memory_footprint)
set_footprint_bytes(*memory_footprint);
}
#endif // defined(OS_CHROMEOS)
void TaskGroup::OnBackgroundRefreshTypeFinished(int64_t finished_refresh_type) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
current_on_bg_done_flags_ |= finished_refresh_type;
if (AreBackgroundCalculationsDone())
on_background_calculations_done_.Run();
}
} // namespace task_manager