// Copyright (c) 2012 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 "content/browser/gpu/gpu_internals_ui.h"
#include <string>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/i18n/time_formatting.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/sys_info.h"
#include "base/values.h"
#include "content/browser/gpu/compositor_util.h"
#include "content/browser/gpu/gpu_data_manager_impl.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/gpu_data_manager_observer.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
#include "content/public/browser/web_ui_message_handler.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "gpu/config/gpu_feature_type.h"
#include "gpu/config/gpu_info.h"
#include "grit/content_resources.h"
#include "third_party/angle/src/common/version.h"
namespace content {
namespace {
WebUIDataSource* CreateGpuHTMLSource() {
WebUIDataSource* source = WebUIDataSource::Create(kChromeUIGpuHost);
source->SetJsonPath("strings.js");
source->AddResourcePath("gpu_internals.js", IDR_GPU_INTERNALS_JS);
source->SetDefaultResource(IDR_GPU_INTERNALS_HTML);
return source;
}
base::DictionaryValue* NewDescriptionValuePair(const std::string& desc,
const std::string& value) {
base::DictionaryValue* dict = new base::DictionaryValue();
dict->SetString("description", desc);
dict->SetString("value", value);
return dict;
}
base::DictionaryValue* NewDescriptionValuePair(const std::string& desc,
base::Value* value) {
base::DictionaryValue* dict = new base::DictionaryValue();
dict->SetString("description", desc);
dict->Set("value", value);
return dict;
}
#if defined(OS_WIN)
// Output DxDiagNode tree as nested array of {description,value} pairs
base::ListValue* DxDiagNodeToList(const gpu::DxDiagNode& node) {
base::ListValue* list = new base::ListValue();
for (std::map<std::string, std::string>::const_iterator it =
node.values.begin();
it != node.values.end();
++it) {
list->Append(NewDescriptionValuePair(it->first, it->second));
}
for (std::map<std::string, gpu::DxDiagNode>::const_iterator it =
node.children.begin();
it != node.children.end();
++it) {
base::ListValue* sublist = DxDiagNodeToList(it->second);
list->Append(NewDescriptionValuePair(it->first, sublist));
}
return list;
}
#endif
std::string GPUDeviceToString(const gpu::GPUInfo::GPUDevice& gpu) {
std::string vendor = base::StringPrintf("0x%04x", gpu.vendor_id);
if (!gpu.vendor_string.empty())
vendor += " [" + gpu.vendor_string + "]";
std::string device = base::StringPrintf("0x%04x", gpu.device_id);
if (!gpu.device_string.empty())
device += " [" + gpu.device_string + "]";
return base::StringPrintf(
"VENDOR = %s, DEVICE= %s", vendor.c_str(), device.c_str());
}
base::DictionaryValue* GpuInfoAsDictionaryValue() {
gpu::GPUInfo gpu_info = GpuDataManagerImpl::GetInstance()->GetGPUInfo();
base::ListValue* basic_info = new base::ListValue();
basic_info->Append(NewDescriptionValuePair(
"Initialization time",
base::Int64ToString(gpu_info.initialization_time.InMilliseconds())));
basic_info->Append(NewDescriptionValuePair(
"Sandboxed", new base::FundamentalValue(gpu_info.sandboxed)));
basic_info->Append(NewDescriptionValuePair(
"GPU0", GPUDeviceToString(gpu_info.gpu)));
for (size_t i = 0; i < gpu_info.secondary_gpus.size(); ++i) {
basic_info->Append(NewDescriptionValuePair(
base::StringPrintf("GPU%d", static_cast<int>(i + 1)),
GPUDeviceToString(gpu_info.secondary_gpus[i])));
}
basic_info->Append(NewDescriptionValuePair(
"Optimus", new base::FundamentalValue(gpu_info.optimus)));
basic_info->Append(NewDescriptionValuePair(
"AMD switchable", new base::FundamentalValue(gpu_info.amd_switchable)));
if (gpu_info.lenovo_dcute) {
basic_info->Append(NewDescriptionValuePair(
"Lenovo dCute", new base::FundamentalValue(true)));
}
if (gpu_info.display_link_version.IsValid()) {
basic_info->Append(NewDescriptionValuePair(
"DisplayLink Version", gpu_info.display_link_version.GetString()));
}
basic_info->Append(NewDescriptionValuePair("Driver vendor",
gpu_info.driver_vendor));
basic_info->Append(NewDescriptionValuePair("Driver version",
gpu_info.driver_version));
basic_info->Append(NewDescriptionValuePair("Driver date",
gpu_info.driver_date));
basic_info->Append(NewDescriptionValuePair("Pixel shader version",
gpu_info.pixel_shader_version));
basic_info->Append(NewDescriptionValuePair("Vertex shader version",
gpu_info.vertex_shader_version));
basic_info->Append(NewDescriptionValuePair("Machine model",
gpu_info.machine_model));
basic_info->Append(NewDescriptionValuePair("GL version",
gpu_info.gl_version));
basic_info->Append(NewDescriptionValuePair("GL_VENDOR",
gpu_info.gl_vendor));
basic_info->Append(NewDescriptionValuePair("GL_RENDERER",
gpu_info.gl_renderer));
basic_info->Append(NewDescriptionValuePair("GL_VERSION",
gpu_info.gl_version_string));
basic_info->Append(NewDescriptionValuePair("GL_EXTENSIONS",
gpu_info.gl_extensions));
basic_info->Append(NewDescriptionValuePair("Window system binding vendor",
gpu_info.gl_ws_vendor));
basic_info->Append(NewDescriptionValuePair("Window system binding version",
gpu_info.gl_ws_version));
basic_info->Append(NewDescriptionValuePair("Window system binding extensions",
gpu_info.gl_ws_extensions));
std::string reset_strategy =
base::StringPrintf("0x%04x", gpu_info.gl_reset_notification_strategy);
basic_info->Append(NewDescriptionValuePair(
"Reset notification strategy", reset_strategy));
base::DictionaryValue* info = new base::DictionaryValue();
info->Set("basic_info", basic_info);
#if defined(OS_WIN)
base::ListValue* perf_info = new base::ListValue();
perf_info->Append(NewDescriptionValuePair(
"Graphics",
base::StringPrintf("%.1f", gpu_info.performance_stats.graphics)));
perf_info->Append(NewDescriptionValuePair(
"Gaming",
base::StringPrintf("%.1f", gpu_info.performance_stats.gaming)));
perf_info->Append(NewDescriptionValuePair(
"Overall",
base::StringPrintf("%.1f", gpu_info.performance_stats.overall)));
info->Set("performance_info", perf_info);
base::Value* dx_info = gpu_info.dx_diagnostics.children.size() ?
DxDiagNodeToList(gpu_info.dx_diagnostics) :
base::Value::CreateNullValue();
info->Set("diagnostics", dx_info);
#endif
return info;
}
// This class receives javascript messages from the renderer.
// Note that the WebUI infrastructure runs on the UI thread, therefore all of
// this class's methods are expected to run on the UI thread.
class GpuMessageHandler
: public WebUIMessageHandler,
public base::SupportsWeakPtr<GpuMessageHandler>,
public GpuDataManagerObserver {
public:
GpuMessageHandler();
virtual ~GpuMessageHandler();
// WebUIMessageHandler implementation.
virtual void RegisterMessages() OVERRIDE;
// GpuDataManagerObserver implementation.
virtual void OnGpuInfoUpdate() OVERRIDE;
virtual void OnGpuSwitching() OVERRIDE;
// Messages
void OnBrowserBridgeInitialized(const base::ListValue* list);
void OnCallAsync(const base::ListValue* list);
// Submessages dispatched from OnCallAsync
base::Value* OnRequestClientInfo(const base::ListValue* list);
base::Value* OnRequestLogMessages(const base::ListValue* list);
private:
// True if observing the GpuDataManager (re-attaching as observer would
// DCHECK).
bool observing_;
DISALLOW_COPY_AND_ASSIGN(GpuMessageHandler);
};
////////////////////////////////////////////////////////////////////////////////
//
// GpuMessageHandler
//
////////////////////////////////////////////////////////////////////////////////
GpuMessageHandler::GpuMessageHandler()
: observing_(false) {
}
GpuMessageHandler::~GpuMessageHandler() {
GpuDataManagerImpl::GetInstance()->RemoveObserver(this);
}
/* BrowserBridge.callAsync prepends a requestID to these messages. */
void GpuMessageHandler::RegisterMessages() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
web_ui()->RegisterMessageCallback("browserBridgeInitialized",
base::Bind(&GpuMessageHandler::OnBrowserBridgeInitialized,
base::Unretained(this)));
web_ui()->RegisterMessageCallback("callAsync",
base::Bind(&GpuMessageHandler::OnCallAsync,
base::Unretained(this)));
}
void GpuMessageHandler::OnCallAsync(const base::ListValue* args) {
DCHECK_GE(args->GetSize(), static_cast<size_t>(2));
// unpack args into requestId, submessage and submessageArgs
bool ok;
const base::Value* requestId;
ok = args->Get(0, &requestId);
DCHECK(ok);
std::string submessage;
ok = args->GetString(1, &submessage);
DCHECK(ok);
base::ListValue* submessageArgs = new base::ListValue();
for (size_t i = 2; i < args->GetSize(); ++i) {
const base::Value* arg;
ok = args->Get(i, &arg);
DCHECK(ok);
base::Value* argCopy = arg->DeepCopy();
submessageArgs->Append(argCopy);
}
// call the submessage handler
base::Value* ret = NULL;
if (submessage == "requestClientInfo") {
ret = OnRequestClientInfo(submessageArgs);
} else if (submessage == "requestLogMessages") {
ret = OnRequestLogMessages(submessageArgs);
} else { // unrecognized submessage
NOTREACHED();
delete submessageArgs;
return;
}
delete submessageArgs;
// call BrowserBridge.onCallAsyncReply with result
if (ret) {
web_ui()->CallJavascriptFunction("browserBridge.onCallAsyncReply",
*requestId,
*ret);
delete ret;
} else {
web_ui()->CallJavascriptFunction("browserBridge.onCallAsyncReply",
*requestId);
}
}
void GpuMessageHandler::OnBrowserBridgeInitialized(
const base::ListValue* args) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// Watch for changes in GPUInfo
if (!observing_)
GpuDataManagerImpl::GetInstance()->AddObserver(this);
observing_ = true;
// Tell GpuDataManager it should have full GpuInfo. If the
// Gpu process has not run yet, this will trigger its launch.
GpuDataManagerImpl::GetInstance()->RequestCompleteGpuInfoIfNeeded();
// Run callback immediately in case the info is ready and no update in the
// future.
OnGpuInfoUpdate();
}
base::Value* GpuMessageHandler::OnRequestClientInfo(
const base::ListValue* list) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
base::DictionaryValue* dict = new base::DictionaryValue();
dict->SetString("version", GetContentClient()->GetProduct());
dict->SetString("command_line",
CommandLine::ForCurrentProcess()->GetCommandLineString());
dict->SetString("operating_system",
base::SysInfo::OperatingSystemName() + " " +
base::SysInfo::OperatingSystemVersion());
dict->SetString("angle_revision", base::UintToString(BUILD_REVISION));
dict->SetString("graphics_backend", "Skia");
dict->SetString("blacklist_version",
GpuDataManagerImpl::GetInstance()->GetBlacklistVersion());
dict->SetString("driver_bug_list_version",
GpuDataManagerImpl::GetInstance()->GetDriverBugListVersion());
return dict;
}
base::Value* GpuMessageHandler::OnRequestLogMessages(const base::ListValue*) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
return GpuDataManagerImpl::GetInstance()->GetLogMessages();
}
void GpuMessageHandler::OnGpuInfoUpdate() {
// Get GPU Info.
scoped_ptr<base::DictionaryValue> gpu_info_val(GpuInfoAsDictionaryValue());
// Add in blacklisting features
base::DictionaryValue* feature_status = new DictionaryValue;
feature_status->Set("featureStatus", GetFeatureStatus());
feature_status->Set("problems", GetProblems());
feature_status->Set("workarounds", GetDriverBugWorkarounds());
if (feature_status)
gpu_info_val->Set("featureStatus", feature_status);
// Send GPU Info to javascript.
web_ui()->CallJavascriptFunction("browserBridge.onGpuInfoUpdate",
*(gpu_info_val.get()));
}
void GpuMessageHandler::OnGpuSwitching() {
GpuDataManagerImpl::GetInstance()->RequestCompleteGpuInfoIfNeeded();
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
//
// GpuInternalsUI
//
////////////////////////////////////////////////////////////////////////////////
GpuInternalsUI::GpuInternalsUI(WebUI* web_ui)
: WebUIController(web_ui) {
web_ui->AddMessageHandler(new GpuMessageHandler());
// Set up the chrome://gpu/ source.
BrowserContext* browser_context =
web_ui->GetWebContents()->GetBrowserContext();
WebUIDataSource::Add(browser_context, CreateGpuHTMLSource());
}
} // namespace content