blob: a2c72b09dbce18d7bfe892169c0ede886261dc78 [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_management/sampling/task_group.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/stl_util.h"
#include "build/build_config.h"
#include "chrome/browser/task_management/task_manager_observer.h"
#include "components/nacl/browser/nacl_browser.h"
#include "content/public/browser/browser_thread.h"
#include "gpu/ipc/common/memory_stats.h"
namespace task_management {
namespace {
// A mask for the refresh types that are done in the background thread.
const int kBackgroundRefreshTypesMask =
REFRESH_TYPE_CPU |
REFRESH_TYPE_MEMORY |
REFRESH_TYPE_IDLE_WAKEUPS |
#if defined(OS_LINUX)
REFRESH_TYPE_FD_COUNT |
#endif // defined(OS_LINUX)
REFRESH_TYPE_PRIORITY;
inline bool IsResourceRefreshEnabled(RefreshType refresh_type,
int refresh_flags) {
return (refresh_flags & refresh_type) != 0;
}
#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)
} // namespace
TaskGroup::TaskGroup(
base::ProcessHandle proc_handle,
base::ProcessId proc_id,
const base::Closure& on_background_calculations_done,
const scoped_refptr<base::SequencedTaskRunner>& blocking_pool_runner)
: process_handle_(proc_handle),
process_id_(proc_id),
on_background_calculations_done_(on_background_calculations_done),
worker_thread_sampler_(nullptr),
expected_on_bg_done_flags_(kBackgroundRefreshTypesMask),
current_on_bg_done_flags_(0),
cpu_usage_(0.0),
memory_usage_(),
gpu_memory_(-1),
per_process_network_usage_(-1),
#if defined(OS_WIN)
gdi_current_handles_(-1),
gdi_peak_handles_(-1),
user_current_handles_(-1),
user_peak_handles_(-1),
#endif // defined(OS_WIN)
#if !defined(DISABLE_NACL)
nacl_debug_stub_port_(-1),
#endif // !defined(DISABLE_NACL)
idle_wakeups_per_second_(-1),
#if defined(OS_LINUX)
open_fd_count_(-1),
#endif // defined(OS_LINUX)
gpu_memory_has_duplicates_(false),
is_backgrounded_(false),
weak_ptr_factory_(this) {
scoped_refptr<TaskGroupSampler> sampler(
new TaskGroupSampler(base::Process::Open(proc_id),
blocking_pool_runner,
base::Bind(&TaskGroup::OnCpuRefreshDone,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&TaskGroup::OnMemoryUsageRefreshDone,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&TaskGroup::OnIdleWakeupsRefreshDone,
weak_ptr_factory_.GetWeakPtr()),
#if defined(OS_LINUX)
base::Bind(&TaskGroup::OnOpenFdCountRefreshDone,
weak_ptr_factory_.GetWeakPtr()),
#endif // defined(OS_LINUX)
base::Bind(&TaskGroup::OnProcessPriorityDone,
weak_ptr_factory_.GetWeakPtr())));
worker_thread_sampler_.swap(sampler);
}
TaskGroup::~TaskGroup() {
}
void TaskGroup::AddTask(Task* task) {
DCHECK(task);
DCHECK(task->process_id() == process_id_);
DCHECK(!ContainsKey(tasks_, task->task_id()));
tasks_[task->task_id()] = task;
}
void TaskGroup::RemoveTask(Task* task) {
DCHECK(task);
DCHECK(ContainsKey(tasks_, task->task_id()));
tasks_.erase(task->task_id());
}
void TaskGroup::Refresh(const gpu::VideoMemoryUsageStats& gpu_memory_stats,
base::TimeDelta update_interval,
int64_t refresh_flags) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
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 =
IsResourceRefreshEnabled(REFRESH_TYPE_NETWORK_USAGE, refresh_flags);
per_process_network_usage_ = network_usage_refresh_enabled ? 0 : -1;
for (auto& task_pair : tasks_) {
Task* task = task_pair.second;
task->Refresh(update_interval, refresh_flags);
if (network_usage_refresh_enabled && task->ReportsNetworkUsage())
per_process_network_usage_ += task->network_usage();
}
// 2- Refresh GPU memory (if enabled).
if (IsResourceRefreshEnabled(REFRESH_TYPE_GPU_MEMORY, refresh_flags))
RefreshGpuMemory(gpu_memory_stats);
// 3- Refresh Windows handles (if enabled).
#if defined(OS_WIN)
if (IsResourceRefreshEnabled(REFRESH_TYPE_HANDLES, refresh_flags))
RefreshWindowsHandles();
#endif // defined(OS_WIN)
// 4- Refresh the NACL debug stub port (if enabled).
#if !defined(DISABLE_NACL)
if (IsResourceRefreshEnabled(REFRESH_TYPE_NACL, refresh_flags) &&
!tasks_.empty()) {
RefreshNaClDebugStubPort(tasks_.begin()->second->GetChildProcessUniqueID());
}
#endif // !defined(DISABLE_NACL)
// 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.
// 5- CPU usage.
// 6- Memory usage.
// 7- Idle Wakeups per second.
// 8- (Linux and ChromeOS only) The number of file descriptors current open.
// 9- Process priority (foreground vs. background).
worker_thread_sampler_->Refresh(refresh_flags);
}
void TaskGroup::AppendSortedTaskIds(TaskIdList* out_list) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(out_list);
for (const auto& task_pair : tasks_)
out_list->push_back(task_pair.first);
}
Task* TaskGroup::GetTaskById(TaskId task_id) const {
DCHECK(ContainsKey(tasks_, task_id));
return tasks_.at(task_id);
}
void TaskGroup::ClearCurrentBackgroundCalculationsFlags() {
current_on_bg_done_flags_ = 0;
}
bool TaskGroup::AreBackgroundCalculationsDone() const {
return expected_on_bg_done_flags_ == current_on_bg_done_flags_;
}
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)
}
void TaskGroup::RefreshNaClDebugStubPort(int child_process_unique_id) {
#if !defined(DISABLE_NACL)
nacl::NaClBrowser* nacl_browser = nacl::NaClBrowser::GetInstance();
nacl_debug_stub_port_ =
nacl_browser->GetProcessGdbDebugStubPort(child_process_unique_id);
#endif // !defined(DISABLE_NACL)
}
void TaskGroup::OnCpuRefreshDone(double cpu_usage) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
cpu_usage_ = cpu_usage;
OnBackgroundRefreshTypeFinished(REFRESH_TYPE_CPU);
}
void TaskGroup::OnMemoryUsageRefreshDone(MemoryUsageStats memory_usage) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
memory_usage_ = memory_usage;
OnBackgroundRefreshTypeFinished(REFRESH_TYPE_MEMORY);
}
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);
}
#if defined(OS_LINUX)
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)
void TaskGroup::OnProcessPriorityDone(bool is_backgrounded) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
is_backgrounded_ = is_backgrounded;
OnBackgroundRefreshTypeFinished(REFRESH_TYPE_PRIORITY);
}
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_management