Ash HUD Tracing: Add "Start tracing" button to HUD.
This CL enables simple tracing control from Ash HUD.
Perfetto trace is always saved to /tmp/ash_trace.dat
(hard-coded file name).
DD: https://goto.google.com/ash-tracing
Bug: 1111854
Test: browser_tests --gtest_filter='All/AshHUDLoginTest.AshHUDVerifyTracing/*'
Change-Id: I14a00d2652bb97aa830f194baddfc77164aa4ebc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2597749
Reviewed-by: Dirk Pranke <[email protected]>
Reviewed-by: Mitsuru Oshima <[email protected]>
Reviewed-by: Sami Kyöstilä <[email protected]>
Commit-Queue: Alexander Alekseev <[email protected]>
Cr-Commit-Position: refs/heads/master@{#879106}
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 12442ea54..d7c50e9 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -515,6 +515,12 @@
"host/root_window_transformer.h",
"host/transformer_helper.cc",
"host/transformer_helper.h",
+ "hud_display/ash_tracing_handler.cc",
+ "hud_display/ash_tracing_handler.h",
+ "hud_display/ash_tracing_manager.cc",
+ "hud_display/ash_tracing_manager.h",
+ "hud_display/ash_tracing_request.cc",
+ "hud_display/ash_tracing_request.h",
"hud_display/cpu_graph_page_view.cc",
"hud_display/cpu_graph_page_view.h",
"hud_display/cpu_status.cc",
@@ -1888,6 +1894,7 @@
# TODO(msw): Remove this; ash should not depend on blink/webkit.
"//third_party/blink/public:blink_headers",
"//third_party/icu",
+ "//third_party/perfetto:libperfetto",
"//third_party/qcms",
"//third_party/re2",
"//ui/accessibility",
diff --git a/ash/hud_display/DEPS b/ash/hud_display/DEPS
new file mode 100644
index 0000000..58d2ae3
--- /dev/null
+++ b/ash/hud_display/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+services/tracing/public",
+ "+third_party/perfetto/include/perfetto/tracing/core/trace_config.h",
+ "+third_party/perfetto/include/perfetto/tracing/tracing.h",
+]
diff --git a/ash/hud_display/ash_tracing_handler.cc b/ash/hud_display/ash_tracing_handler.cc
new file mode 100644
index 0000000..92f763ee
--- /dev/null
+++ b/ash/hud_display/ash_tracing_handler.cc
@@ -0,0 +1,112 @@
+// Copyright 2021 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 "ash/hud_display/ash_tracing_handler.h"
+
+#include <sys/mman.h>
+
+#include <algorithm>
+
+#include "ash/hud_display/ash_tracing_request.h"
+#include "ash/shell.h"
+#include "base/bind.h"
+#include "base/files/file.h"
+#include "base/files/platform_file.h"
+#include "base/logging.h"
+#include "base/no_destructor.h"
+#include "base/stl_util.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "services/tracing/public/cpp/perfetto/perfetto_config.h"
+#include "third_party/perfetto/include/perfetto/tracing/core/trace_config.h"
+#include "third_party/perfetto/include/perfetto/tracing/tracing.h"
+
+namespace ash {
+namespace hud_display {
+namespace {
+
+std::unique_ptr<perfetto::TracingSession> (*testing_perfetto_session_creator)(
+ void) = nullptr;
+
+} // anonymous namespace
+
+AshTracingHandler::AshTracingHandler() {
+ // Bind sequence checker.
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+}
+
+AshTracingHandler::~AshTracingHandler() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+}
+
+void AshTracingHandler::Start(AshTracingRequest* request) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+ DCHECK(!request_);
+ DCHECK(!tracing_session_);
+
+ request_ = request;
+ perfetto::TraceConfig perfetto_config = tracing::GetDefaultPerfettoConfig(
+ base::trace_event::TraceConfig(),
+ /*privacy_filtering_enabled=*/false,
+ /*convert_to_legacy_json=*/false,
+ perfetto::protos::gen::ChromeConfig::USER_INITIATED);
+
+ perfetto_config.set_write_into_file(true);
+ tracing_session_ = testing_perfetto_session_creator
+ ? testing_perfetto_session_creator()
+ : perfetto::Tracing::NewTrace();
+ tracing_session_->Setup(perfetto_config, request->GetPlatformFile());
+ auto runner = base::SequencedTaskRunnerHandle::Get();
+ base::WeakPtr<AshTracingHandler> weak_ptr = weak_factory_.GetWeakPtr();
+ tracing_session_->SetOnStartCallback([runner, weak_ptr]() {
+ runner->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AshTracingHandler::OnTracingStarted, weak_ptr));
+ });
+ tracing_session_->SetOnStopCallback([runner, weak_ptr]() {
+ runner->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AshTracingHandler::OnTracingFinished, weak_ptr));
+ });
+ tracing_session_->Start();
+}
+
+void AshTracingHandler::Stop() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+ DCHECK(tracing_session_);
+ tracing_session_->Stop();
+}
+
+bool AshTracingHandler::IsStarted() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+ return static_cast<bool>(tracing_session_);
+}
+
+// static
+void AshTracingHandler::SetPerfettoTracingSessionCreatorForTesting(
+ std::unique_ptr<perfetto::TracingSession> (*creator)(void)) {
+ DCHECK(!testing_perfetto_session_creator);
+ testing_perfetto_session_creator = creator;
+}
+
+// static
+void AshTracingHandler::ResetPerfettoTracingSessionCreatorForTesting() {
+ DCHECK(testing_perfetto_session_creator);
+ testing_perfetto_session_creator = nullptr;
+}
+
+void AshTracingHandler::OnTracingStarted() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+ request_->OnTracingStarted();
+}
+
+void AshTracingHandler::OnTracingFinished() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+ tracing_session_.reset();
+ request_->OnTracingFinished();
+}
+
+} // namespace hud_display
+} // namespace ash
diff --git a/ash/hud_display/ash_tracing_handler.h b/ash/hud_display/ash_tracing_handler.h
new file mode 100644
index 0000000..edcf086
--- /dev/null
+++ b/ash/hud_display/ash_tracing_handler.h
@@ -0,0 +1,62 @@
+// Copyright 2021 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.
+
+#ifndef ASH_HUD_DISPLAY_ASH_TRACING_HANDLER_H_
+#define ASH_HUD_DISPLAY_ASH_TRACING_HANDLER_H_
+
+#include <memory>
+
+#include "ash/ash_export.h"
+#include "base/files/platform_file.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/trace_event/trace_config.h"
+
+namespace perfetto {
+class TracingSession;
+}
+
+namespace ash {
+namespace hud_display {
+
+class AshTracingRequest;
+
+// Only one instance of this object can exist at a time.
+class ASH_EXPORT AshTracingHandler {
+ public:
+ AshTracingHandler();
+ AshTracingHandler(const AshTracingHandler&) = delete;
+ AshTracingHandler& operator=(const AshTracingHandler&) = delete;
+ ~AshTracingHandler();
+
+ // Initiates tracing start. Observer will be notified with the result.
+ void Start(AshTracingRequest* request);
+
+ // Initiates tracing stop. Observer will be notified with the result.
+ void Stop();
+
+ // Returns true if tracing was started.
+ bool IsStarted() const;
+
+ // This allows to use fake Perfetto sessions for testing.
+ static void SetPerfettoTracingSessionCreatorForTesting(
+ std::unique_ptr<perfetto::TracingSession> (*creator)(void));
+ static void ResetPerfettoTracingSessionCreatorForTesting();
+
+ private:
+ void OnTracingStarted();
+ void OnTracingFinished();
+
+ AshTracingRequest* request_ = nullptr;
+
+ std::unique_ptr<perfetto::TracingSession> tracing_session_;
+
+ SEQUENCE_CHECKER(my_sequence_checker_);
+ base::WeakPtrFactory<AshTracingHandler> weak_factory_{this};
+};
+
+} // namespace hud_display
+} // namespace ash
+
+#endif // ASH_HUD_DISPLAY_ASH_TRACING_HANDLER_H_
diff --git a/ash/hud_display/ash_tracing_manager.cc b/ash/hud_display/ash_tracing_manager.cc
new file mode 100644
index 0000000..9b4bf462
--- /dev/null
+++ b/ash/hud_display/ash_tracing_manager.cc
@@ -0,0 +1,177 @@
+// Copyright 2021 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 "ash/hud_display/ash_tracing_manager.h"
+
+#include <vector>
+
+#include "ash/hud_display/ash_tracing_request.h"
+#include "ash/session/session_controller_impl.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/no_destructor.h"
+#include "base/sequence_checker.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+
+namespace ash {
+namespace hud_display {
+
+AshTracingManager::AshTracingManager() {
+ SessionController::Get()->AddObserver(this);
+}
+
+AshTracingManager::~AshTracingManager() {
+ if (SessionController::Get())
+ SessionController::Get()->RemoveObserver(this);
+}
+
+// static
+AshTracingManager& AshTracingManager::Get() {
+ static base::NoDestructor<AshTracingManager> manager;
+ return *manager;
+}
+
+bool AshTracingManager::IsBusy() const {
+ if (tracing_requests_.empty())
+ return false;
+
+ switch (GetLastRequestStatus()) {
+ case AshTracingRequest::Status::kEmpty:
+ FALLTHROUGH;
+ case AshTracingRequest::Status::kInitialized:
+ return true;
+ case AshTracingRequest::Status::kStarted:
+ return false;
+ case AshTracingRequest::Status::kStopping:
+ return true;
+ case AshTracingRequest::Status::kPendingMount:
+ FALLTHROUGH;
+ case AshTracingRequest::Status::kWritingFile:
+ FALLTHROUGH;
+ case AshTracingRequest::Status::kCompleted:
+ return false;
+ }
+}
+
+bool AshTracingManager::IsTracingStarted() const {
+ if (tracing_requests_.empty())
+ return false;
+
+ switch (GetLastRequestStatus()) {
+ case AshTracingRequest::Status::kEmpty:
+ FALLTHROUGH;
+ case AshTracingRequest::Status::kInitialized:
+ FALLTHROUGH;
+ case AshTracingRequest::Status::kStarted:
+ FALLTHROUGH;
+ case AshTracingRequest::Status::kStopping:
+ return true;
+ case AshTracingRequest::Status::kPendingMount:
+ FALLTHROUGH;
+ case AshTracingRequest::Status::kWritingFile:
+ FALLTHROUGH;
+ case AshTracingRequest::Status::kCompleted:
+ return false;
+ }
+}
+
+std::string AshTracingManager::GetStatusMessage() const {
+ std::string result;
+ if (tracing_requests_.empty())
+ return result;
+
+ unsigned started = 0;
+ unsigned waiting_for_login = 0;
+ unsigned writing_trace_file = 0;
+ unsigned completed = 0;
+ for (const auto& request : tracing_requests_) {
+ switch (request->status()) {
+ case AshTracingRequest::Status::kEmpty:
+ FALLTHROUGH;
+ case AshTracingRequest::Status::kInitialized:
+ FALLTHROUGH;
+ case AshTracingRequest::Status::kStarted:
+ FALLTHROUGH;
+ case AshTracingRequest::Status::kStopping:
+ ++started;
+ break;
+ case AshTracingRequest::Status::kPendingMount:
+ ++waiting_for_login;
+ break;
+ case AshTracingRequest::Status::kWritingFile:
+ ++writing_trace_file;
+ break;
+ case AshTracingRequest::Status::kCompleted:
+ ++completed;
+ }
+ }
+ if (started)
+ result = base::StringPrintf("%u active", started);
+
+ if (waiting_for_login) {
+ if (!result.empty())
+ result += ", ";
+
+ result += base::StringPrintf("%u pending login", waiting_for_login);
+ }
+ if (writing_trace_file) {
+ if (!result.empty())
+ result += ", ";
+
+ result += base::StringPrintf("%u writing", writing_trace_file);
+ }
+ if (completed) {
+ if (!result.empty())
+ result += ", ";
+
+ result += base::StringPrintf("%u completed", completed);
+ }
+ if (!result.empty())
+ result = std::string("Tracing: ") + result + ".";
+
+ return result;
+}
+
+void AshTracingManager::Start() {
+ DCHECK(!IsBusy());
+ DCHECK(!IsTracingStarted());
+ tracing_requests_.push_back(std::make_unique<AshTracingRequest>(this));
+}
+
+void AshTracingManager::Stop() {
+ DCHECK(!tracing_requests_.empty());
+ DCHECK_EQ(GetLastRequestStatus(), AshTracingRequest::Status::kStarted);
+ tracing_requests_.back()->Stop();
+}
+
+void AshTracingManager::AddObserver(AshTracingManager::Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void AshTracingManager::RemoveObserver(AshTracingManager::Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void AshTracingManager::OnRequestStatusChanged(AshTracingRequest* request) {
+ for (Observer& observer : observers_)
+ observer.OnTracingStatusChange();
+}
+
+void AshTracingManager::OnFirstSessionStarted() {
+ for (auto& request : tracing_requests_)
+ request->OnUserLoggedIn();
+}
+
+const std::vector<std::unique_ptr<AshTracingRequest>>&
+AshTracingManager::GetTracingRequestsForTesting() const {
+ return tracing_requests_;
+}
+
+AshTracingRequest::Status AshTracingManager::GetLastRequestStatus() const {
+ return tracing_requests_.back()->status();
+}
+
+} // namespace hud_display
+} // namespace ash
diff --git a/ash/hud_display/ash_tracing_manager.h b/ash/hud_display/ash_tracing_manager.h
new file mode 100644
index 0000000..050eb9f
--- /dev/null
+++ b/ash/hud_display/ash_tracing_manager.h
@@ -0,0 +1,81 @@
+// Copyright 2021 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.
+
+#ifndef ASH_HUD_DISPLAY_ASH_TRACING_MANAGER_H_
+#define ASH_HUD_DISPLAY_ASH_TRACING_MANAGER_H_
+
+#include <memory>
+
+#include "ash/hud_display/ash_tracing_request.h"
+#include "ash/public/cpp/session/session_observer.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/observer_list_types.h"
+
+namespace ash {
+namespace hud_display {
+
+// Singleton object to manager Ash tracing sessions.
+class ASH_EXPORT AshTracingManager : public SessionObserver {
+ public:
+ class Observer : public base::CheckedObserver {
+ public:
+ Observer() = default;
+ ~Observer() override = default;
+
+ virtual void OnTracingStatusChange() = 0;
+ };
+
+ AshTracingManager();
+ AshTracingManager(const AshTracingManager&) = delete;
+ AshTracingManager& operator=(const AshTracingManager&) = delete;
+ ~AshTracingManager() override;
+
+ static AshTracingManager& Get();
+
+ // True when tracing is being started or stopped. No control requests are
+ // possible.
+ bool IsBusy() const;
+
+ // True when tracing can be stopped.
+ bool IsTracingStarted() const;
+
+ // Returns user status message.
+ std::string GetStatusMessage() const;
+
+ // Initiates asynchronous tracing start. Observer will be notified with the
+ // result.
+ void Start();
+
+ // Initiates asynchronous tracing stop. Observer will be notified with the
+ // result.
+ void Stop();
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Called by AshTracingRequest to update system state.
+ void OnRequestStatusChanged(AshTracingRequest* request);
+
+ // SessionObserver
+ void OnFirstSessionStarted() override;
+
+ const std::vector<std::unique_ptr<AshTracingRequest>>&
+ GetTracingRequestsForTesting() const;
+
+ private:
+ // Returns status of the last request in tracing_requests_.
+ AshTracingRequest::Status GetLastRequestStatus() const;
+
+ // Only last tracing request can be active. Other can be either finished, or
+ // waiting for user session start to save the trace.
+ std::vector<std::unique_ptr<AshTracingRequest>> tracing_requests_;
+
+ base::ObserverList<Observer> observers_;
+};
+
+} // namespace hud_display
+} // namespace ash
+
+#endif // ASH_HUD_DISPLAY_ASH_TRACING_MANAGER_H_
diff --git a/ash/hud_display/ash_tracing_request.cc b/ash/hud_display/ash_tracing_request.cc
new file mode 100644
index 0000000..f588067
--- /dev/null
+++ b/ash/hud_display/ash_tracing_request.cc
@@ -0,0 +1,409 @@
+// Copyright 2021 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 "ash/hud_display/ash_tracing_request.h"
+
+#include <sys/mman.h>
+#include <sys/sendfile.h>
+
+#include "ash/hud_display/ash_tracing_handler.h"
+#include "ash/hud_display/ash_tracing_manager.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/platform_file.h"
+#include "base/logging.h"
+#include "base/posix/safe_strerror.h"
+#include "base/strings/stringprintf.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+
+namespace ash {
+namespace hud_display {
+namespace {
+
+// Tests supply their own IO layer generator.
+static std::unique_ptr<AshTraceDestinationIO> (
+ *test_ash_trace_destination_io_creator)(void) = nullptr;
+
+class DefaultAshTraceDestinationIO : public AshTraceDestinationIO {
+ public:
+ ~DefaultAshTraceDestinationIO() override = default;
+
+ // Overrides base::CreateDirectory.
+ bool CreateDirectory(const base::FilePath& path) override {
+ base::File::Error error;
+ if (!base::CreateDirectoryAndGetError(path, &error)) {
+ LOG(ERROR) << "Failed to create Ash trace file directory '"
+ << path.value() << "' : error " << error;
+ return false;
+ }
+ return true;
+ }
+
+ // Overrides base::File::File(). Returns pair {File file, bool success}.
+ std::tuple<base::File, bool> CreateTracingFile(
+ const base::FilePath& path) override {
+ base::File file(path, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+ const bool success = file.IsValid();
+ return std::make_tuple(std::move(file), success);
+ }
+
+ // Implements memfd_create(2).
+ std::tuple<base::PlatformFile, bool> CreateMemFD(
+ const char* name,
+ unsigned int flags) override {
+ base::PlatformFile memfd = memfd_create(name, flags);
+ return std::make_tuple(memfd, memfd != base::kInvalidPlatformFile);
+ }
+
+ bool CanWriteFile(base::PlatformFile fd) override {
+ return fd != base::kInvalidPlatformFile;
+ }
+
+ int fstat(base::PlatformFile fd, struct stat* statbuf) override {
+ return ::fstat(fd, statbuf);
+ }
+
+ ssize_t sendfile(base::PlatformFile out_fd,
+ base::PlatformFile in_fd,
+ off_t* offset,
+ size_t size) override {
+ return ::sendfile(out_fd, in_fd, offset, size);
+ }
+};
+
+std::string GenerateTraceFileName(base::Time timestamp) {
+ base::Time::Exploded time_deets;
+ timestamp.LocalExplode(&time_deets);
+ return base::StringPrintf(
+ "ash-trace_%02d%02d%02d-%02d%02d%02d.%03d.dat", time_deets.year,
+ time_deets.month, time_deets.day_of_month, time_deets.hour,
+ time_deets.minute, time_deets.second, time_deets.millisecond);
+}
+
+std::unique_ptr<AshTraceDestination> GenerateTraceDestinationFile(
+ std::unique_ptr<AshTraceDestinationIO> io,
+ const base::FilePath& tracng_directory_path,
+ base::Time timestamp) {
+ if (!io->CreateDirectory(tracng_directory_path))
+ return nullptr;
+
+ base::FilePath path =
+ tracng_directory_path.AppendASCII(GenerateTraceFileName(timestamp));
+ base::File file;
+ bool success;
+ std::tie(file, success) = io->CreateTracingFile(path);
+ if (!success) {
+ LOG(ERROR) << "Failed to create Ash trace '" << path.value() << "' : error "
+ << file.error_details();
+ return nullptr;
+ }
+
+ return std::make_unique<AshTraceDestination>(std::move(io), std::move(path),
+ std::move(file),
+ base::kInvalidPlatformFile);
+}
+
+std::unique_ptr<AshTraceDestination> GenerateTraceDestinationMemFD(
+ std::unique_ptr<AshTraceDestinationIO> io) {
+ constexpr char kMemFDDebugName[] = "ash-trace-buffer.dat";
+ base::PlatformFile memfd;
+ bool success;
+ std::tie(memfd, success) = io->CreateMemFD(kMemFDDebugName, MFD_CLOEXEC);
+ if (!success) {
+ LOG(ERROR) << "Failed to create memfd for '" << kMemFDDebugName
+ << "', error:" << base::safe_strerror(errno);
+ return nullptr;
+ }
+ return std::make_unique<AshTraceDestination>(std::move(io), base::FilePath(),
+ base::File(), memfd);
+}
+
+// Must be called with blocking allowed (i.e. from the thread pool).
+// Returns null pointer in case of error.
+std::unique_ptr<AshTraceDestination> GenerateTraceDestination(
+ std::unique_ptr<AshTraceDestinationIO> io,
+ base::Time timestamp,
+ bool is_logging_redirect_disabled,
+ const base::FilePath& user_downloads_folder) {
+ constexpr char kTracingDir[] = "tracing";
+ constexpr char kGlobalTracingPath[] = "/run/chrome/";
+ if (is_logging_redirect_disabled) {
+ return GenerateTraceDestinationFile(
+ std::move(io),
+ base::FilePath(kGlobalTracingPath).AppendASCII(kTracingDir), timestamp);
+ }
+ if (!user_downloads_folder.empty()) {
+ // User already logged in.
+ return GenerateTraceDestinationFile(
+ std::move(io),
+ base::FilePath(user_downloads_folder.AppendASCII(kTracingDir)),
+ timestamp);
+ }
+ // Need to write trace to the user Downloads folder, but it is not available
+ // yet. Create memfd.
+ return GenerateTraceDestinationMemFD(std::move(io));
+}
+
+base::FilePath GetUserDownloadsFolder() {
+ return Shell::Get()->shell_delegate()->GetPrimaryUserDownloadsFolder();
+}
+
+bool IsLoggingRedirectDisabled() {
+ return Shell::Get()->shell_delegate()->IsLoggingRedirectDisabled();
+}
+
+struct ExportStatus {
+ std::unique_ptr<AshTraceDestination> destination;
+ bool success = false;
+ std::string error_message;
+};
+
+ExportStatus ExportDataOnThreadPool(base::PlatformFile memfd,
+ const base::FilePath& user_downloads_folder,
+ base::Time timestamp) {
+ ExportStatus result;
+ result.destination = GenerateTraceDestination(
+ test_ash_trace_destination_io_creator
+ ? test_ash_trace_destination_io_creator()
+ : std::make_unique<DefaultAshTraceDestinationIO>(),
+ timestamp,
+ /*is_logging_redirect_disabled=*/false, user_downloads_folder);
+
+ DCHECK(!result.destination->path().empty());
+
+ struct stat statbuf;
+ if (result.destination->io()->fstat(memfd, &statbuf)) {
+ result.error_message = std::string("Failed to stat memfd, error: ") +
+ base::safe_strerror(errno);
+ LOG(ERROR) << result.error_message;
+ return result;
+ }
+ off_t offset = 0;
+ const ssize_t written = result.destination->io()->sendfile(
+ result.destination->GetPlatformFile(), memfd, &offset, statbuf.st_size);
+ if (written != statbuf.st_size) {
+ const std::string system_error = base::safe_strerror(errno);
+ result.error_message =
+ base::StringPrintf("Stored only %zd trace bytes of %" PRId64 " to '",
+ written, static_cast<int64_t>(statbuf.st_size)) +
+ result.destination->path().value() + "', error: " + system_error;
+ LOG(ERROR) << result.error_message;
+ return result;
+ }
+ result.success = true;
+ return result;
+}
+
+AshTracingRequest::GenerateTraceDestinationTask
+CreateGenerateTraceDestinationTask(std::unique_ptr<AshTraceDestinationIO> io,
+ base::Time timestamp) {
+ return base::BindOnce(&GenerateTraceDestination, std::move(io), timestamp,
+ IsLoggingRedirectDisabled(), GetUserDownloadsFolder());
+}
+
+} // anonymous namespace
+
+AshTraceDestinationIO::~AshTraceDestinationIO() = default;
+
+AshTraceDestination::AshTraceDestination() = default;
+
+AshTraceDestination::AshTraceDestination(
+ std::unique_ptr<AshTraceDestinationIO> io,
+ const base::FilePath& path,
+ base::File&& file,
+ base::PlatformFile fd)
+ : io_(std::move(io)), path_(path), file_(std::move(file)), memfd_(fd) {}
+
+AshTraceDestination::~AshTraceDestination() {
+ Done();
+}
+
+base::PlatformFile AshTraceDestination::GetPlatformFile() const {
+ if (memfd_ != base::kInvalidPlatformFile)
+ return memfd_;
+
+ return file_.GetPlatformFile();
+}
+
+bool AshTraceDestination::CanWriteFile() const {
+ return io_->CanWriteFile(GetPlatformFile());
+}
+
+void AshTraceDestination::Done() {
+ if (memfd_ != base::kInvalidPlatformFile) {
+ base::ThreadPool::PostTask(
+ FROM_HERE,
+ {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+ base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+ base::BindOnce([](base::PlatformFile fd) { close(fd); }, memfd_));
+ memfd_ = base::kInvalidPlatformFile;
+ }
+ if (file_.IsValid()) {
+ base::ThreadPool::PostTask(
+ FROM_HERE,
+ {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+ base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+ base::BindOnce([](base::File fd) { fd.Close(); }, std::move(file_)));
+ }
+}
+
+AshTracingRequest::AshTracingRequest(AshTracingManager* manager)
+ : timestamp_(base::Time::Now()), tracing_manager_(manager) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+ std::unique_ptr<AshTraceDestinationIO> io =
+ test_ash_trace_destination_io_creator
+ ? test_ash_trace_destination_io_creator()
+ : std::make_unique<DefaultAshTraceDestinationIO>();
+ base::ThreadPool::PostTaskAndReplyWithResult(
+ FROM_HERE,
+ {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+ base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+ CreateGenerateTraceDestinationTask(std::move(io), timestamp_),
+ base::BindOnce(&AshTracingRequest::OnTraceDestinationInitialized,
+ weak_factory_.GetWeakPtr()));
+}
+
+AshTracingRequest::~AshTracingRequest() = default;
+
+void AshTracingRequest::Stop() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+ DCHECK(tracing_handler_);
+ status_ = Status::kStopping;
+ tracing_manager_->OnRequestStatusChanged(this);
+ tracing_handler_->Stop();
+}
+
+void AshTracingRequest::OnTracingStarted() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+ status_ = Status::kStarted;
+ tracing_manager_->OnRequestStatusChanged(this);
+}
+
+void AshTracingRequest::OnTracingFinished() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+ tracing_handler_.reset();
+ if (!trace_destination_->path().empty()) {
+ // Trace was already stored to the real file.
+ status_ = Status::kCompleted;
+ trace_destination_->Done();
+ tracing_manager_->OnRequestStatusChanged(this);
+ return;
+ }
+ status_ = Status::kPendingMount;
+ tracing_manager_->OnRequestStatusChanged(this);
+
+ // User logged in while tracing. Need to start saving file immediately.
+ if (user_logged_in_)
+ StorePendingFile();
+}
+
+// Will trigger trace file write if needed.
+// If trace is already finalized, `on_completed` will be called immediately.
+void AshTracingRequest::OnUserLoggedIn() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+ user_logged_in_ = true;
+ StorePendingFile();
+}
+
+base::PlatformFile AshTracingRequest::GetPlatformFile() const {
+ return trace_destination_->GetPlatformFile();
+}
+
+// static
+void AshTracingRequest::SetAshTraceDestinationIOCreatorForTesting(
+ std::unique_ptr<AshTraceDestinationIO> (*creator)(void)) {
+ CHECK(!test_ash_trace_destination_io_creator);
+ test_ash_trace_destination_io_creator = creator;
+}
+
+// static
+void AshTracingRequest::ResetAshTraceDestinationIOCreatorForTesting() {
+ test_ash_trace_destination_io_creator = nullptr;
+}
+
+// static
+AshTracingRequest::GenerateTraceDestinationTask
+AshTracingRequest::CreateGenerateTraceDestinationTaskForTesting(
+ std::unique_ptr<AshTraceDestinationIO> io,
+ base::Time timestamp) {
+ return CreateGenerateTraceDestinationTask(std::move(io), timestamp);
+}
+
+const AshTraceDestination* AshTracingRequest::GetTraceDestinationForTesting()
+ const {
+ return trace_destination_.get();
+}
+
+void AshTracingRequest::OnTraceDestinationInitialized(
+ std::unique_ptr<AshTraceDestination> destination) {
+ DCHECK(!trace_destination_.get());
+ trace_destination_ = std::move(destination);
+ status_ = Status::kInitialized;
+ tracing_manager_->OnRequestStatusChanged(this);
+
+ tracing_handler_ = std::make_unique<AshTracingHandler>();
+ tracing_handler_->Start(this);
+}
+
+void AshTracingRequest::OnPendingFileStored(
+ std::unique_ptr<AshTraceDestination> destination,
+ bool success,
+ std::string error_message) {
+ trace_destination_ = std::move(destination);
+ // Cleanup possible errors.
+ trace_destination_->Done();
+ status_ = Status::kCompleted;
+ error_message_ = error_message;
+ tracing_manager_->OnRequestStatusChanged(this);
+}
+
+void AshTracingRequest::StorePendingFile() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+ if (status_ != Status::kPendingMount)
+ return;
+
+ if (!user_logged_in_)
+ return;
+
+ base::FilePath user_downloads_folder = GetUserDownloadsFolder();
+ if (user_downloads_folder.empty()) {
+ error_message_ = "No user Downloads folder.";
+ status_ = Status::kCompleted;
+ trace_destination_->Done();
+ tracing_manager_->OnRequestStatusChanged(this);
+ return;
+ }
+
+ status_ = Status::kWritingFile;
+ tracing_manager_->OnRequestStatusChanged(this);
+
+ DCHECK(trace_destination_->CanWriteFile());
+
+ base::ThreadPool::PostTaskAndReplyWithResult(
+ FROM_HERE,
+ {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+ base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+ base::BindOnce(&ExportDataOnThreadPool, GetPlatformFile(),
+ GetUserDownloadsFolder(), timestamp_),
+ base::BindOnce(
+ [](base::WeakPtr<AshTracingRequest> self,
+ ExportStatus export_status) {
+ if (!self)
+ return;
+
+ self->OnPendingFileStored(std::move(export_status.destination),
+ export_status.success,
+ export_status.error_message);
+ },
+ weak_factory_.GetWeakPtr()));
+}
+
+} // namespace hud_display
+} // namespace ash
diff --git a/ash/hud_display/ash_tracing_request.h b/ash/hud_display/ash_tracing_request.h
new file mode 100644
index 0000000..30f0ac5
--- /dev/null
+++ b/ash/hud_display/ash_tracing_request.h
@@ -0,0 +1,186 @@
+// Copyright 2021 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.
+
+#ifndef ASH_HUD_DISPLAY_ASH_TRACING_REQUEST_H_
+#define ASH_HUD_DISPLAY_ASH_TRACING_REQUEST_H_
+
+#include <memory>
+
+#include "base/callback_forward.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/platform_file.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/time/time.h"
+
+#include "ash/ash_export.h"
+
+namespace ash {
+namespace hud_display {
+
+class AshTracingManager;
+class AshTracingHandler;
+
+// This is needed for testing to override File IO.
+class ASH_EXPORT AshTraceDestinationIO {
+ public:
+ virtual ~AshTraceDestinationIO();
+
+ // Overrides base::CreateDirectory.
+ virtual bool CreateDirectory(const base::FilePath& path) = 0;
+
+ // Overrides base::File::File(). Returns pair {File file, bool success}.
+ // Test implementation may return success with invalid file.
+ virtual std::tuple<base::File, bool> CreateTracingFile(
+ const base::FilePath& path) = 0;
+
+ // Implements memfd_create(2). Returns pair {int fd, bool success}.
+ // Test implementation may return success with invalid fd.
+ virtual std::tuple<base::PlatformFile, bool> CreateMemFD(
+ const char* name,
+ unsigned int flags) = 0;
+
+ // Takes GetPlatformFile() from AshTraceDestination and returns true if
+ // given fd is valid for storing traces. Checks for -1 in regular case,
+ // and checks internal status in tests.
+ virtual bool CanWriteFile(base::PlatformFile fd) = 0;
+
+ virtual int fstat(base::PlatformFile fd, struct stat* statbuf) = 0;
+
+ virtual ssize_t sendfile(base::PlatformFile out_fd,
+ base::PlatformFile in_fd,
+ off_t* offset,
+ size_t size) = 0;
+};
+
+class ASH_EXPORT AshTraceDestination {
+ public:
+ AshTraceDestination();
+ AshTraceDestination(std::unique_ptr<AshTraceDestinationIO> io,
+ const base::FilePath& path,
+ base::File&& file,
+ base::PlatformFile memfd);
+
+ AshTraceDestination(const AshTraceDestination&) = delete;
+ AshTraceDestination& operator=(const AshTraceDestination&) = delete;
+
+ ~AshTraceDestination();
+
+ const base::FilePath& path() const { return path_; }
+
+ // Returns PlatformFile for storing trace.
+ // Can be memfd or file depending on the setup.
+ base::PlatformFile GetPlatformFile() const;
+
+ // Reurns true if GetPlatformFile() will return valid file descriptor.
+ // In tests when test IO layer is used returns true if test IO layer will
+ // succeed saving file.
+ bool CanWriteFile() const;
+
+ // Close all files.
+ void Done();
+
+ AshTraceDestinationIO* io() const { return io_.get(); }
+
+ private:
+ std::unique_ptr<AshTraceDestinationIO> io_;
+
+ base::FilePath path_;
+ base::File file_;
+ base::PlatformFile memfd_ = base::kInvalidPlatformFile;
+};
+
+class AshTracingRequest {
+ public:
+ enum class Status {
+ kEmpty, // Object created.
+ kInitialized, // File data is initialized
+ kStarted, // Tracing is in progress.
+ kStopping, // Tracing is being stopped.
+ kPendingMount, // Tracing is complete, waiting for home directory mount.
+ kWritingFile, // Writing trace file from memory to file after user login.
+ kCompleted, // Trace file is written. Object has valid path.
+ };
+
+ // Will start tracing (asynchronously).
+ AshTracingRequest(AshTracingManager* tracing_manager);
+ AshTracingRequest(const AshTracingRequest&) = delete;
+ AshTracingRequest& operator=(const AshTracingRequest&) = delete;
+
+ ~AshTracingRequest();
+
+ void Stop();
+
+ // Receive notifications from AshTracingHandler.
+ void OnTracingStarted();
+ void OnTracingFinished();
+
+ // Will trigger trace file write if needed.
+ void OnUserLoggedIn();
+ // Returns file descriptor that will actually be used for tracing.
+ base::PlatformFile GetPlatformFile() const;
+
+ Status status() const { return status_; }
+ const std::string& error_message() const { return error_message_; }
+
+ // Tests generate specific fake IO.
+ static ASH_EXPORT void SetAshTraceDestinationIOCreatorForTesting(
+ std::unique_ptr<AshTraceDestinationIO> (*creator)(void));
+ static ASH_EXPORT void ResetAshTraceDestinationIOCreatorForTesting();
+
+ // Tests explicitly check AshTraceDestination behavior and they need to
+ // be able to generate ThreadPool tasks to crete AshTraceDestination.
+ // So this function will return a task that can be sent to IO-enabled
+ // sequence runner to create AshTraceDestination.
+ using AshTraceDestinationUniquePtr = std::unique_ptr<AshTraceDestination>;
+ using GenerateTraceDestinationTask =
+ base::OnceCallback<AshTraceDestinationUniquePtr(void)>;
+ ASH_EXPORT static GenerateTraceDestinationTask
+ CreateGenerateTraceDestinationTaskForTesting(
+ std::unique_ptr<AshTraceDestinationIO> io,
+ base::Time timestamp);
+
+ ASH_EXPORT const AshTraceDestination* GetTraceDestinationForTesting() const;
+
+ private:
+ // Starts tracing after `destination` was initialized on the ThreadPool.
+ void OnTraceDestinationInitialized(
+ std::unique_ptr<AshTraceDestination> destination);
+
+ // Marks file export operation completed.
+ void OnPendingFileStored(std::unique_ptr<AshTraceDestination> destination,
+ bool success,
+ std::string error_message);
+
+ // Stores memory trace file to permanent location.
+ void StorePendingFile();
+
+ // Trace status
+ Status status_ = Status::kEmpty;
+
+ // When trace was started.
+ const base::Time timestamp_;
+
+ bool user_logged_in_ = false;
+
+ AshTracingManager* tracing_manager_;
+
+ // This object is deleted once tracing is stopped.
+ std::unique_ptr<AshTracingHandler> tracing_handler_;
+
+ // Non-empty if error has occurred.
+ std::string error_message_;
+
+ std::unique_ptr<AshTraceDestination> trace_destination_;
+
+ SEQUENCE_CHECKER(my_sequence_checker_);
+
+ base::WeakPtrFactory<AshTracingRequest> weak_factory_{this};
+};
+
+} // namespace hud_display
+} // namespace ash
+
+#endif // ASH_HUD_DISPLAY_ASH_TRACING_REQUEST_H_
diff --git a/ash/hud_display/graph_page_view_base.cc b/ash/hud_display/graph_page_view_base.cc
index 521b80f..be273af 100644
--- a/ash/hud_display/graph_page_view_base.cc
+++ b/ash/hud_display/graph_page_view_base.cc
@@ -116,6 +116,8 @@
legend_min_max_button_ = legend_container_->AddChildView(
std::make_unique<MinMaxButton>(base::BindRepeating(
&GraphPageViewBase::OnButtonPressed, base::Unretained(this))));
+
+ legend_min_max_button_->SetTooltipText(u"Trigger graph legend");
SetMinimizeIconToButton(legend_min_max_button_);
}
diff --git a/ash/hud_display/hud_constants.h b/ash/hud_display/hud_constants.h
index 9d4cff1c..b712f70 100644
--- a/ash/hud_display/hud_constants.h
+++ b/ash/hud_display/hud_constants.h
@@ -18,6 +18,8 @@
constexpr SkColor kHUDBackground = SkColorSetARGB(kHUDAlpha, 17, 17, 17);
constexpr SkColor kHUDLegendBackground = kHUDBackground;
+constexpr SkColor kHUDDisabledButtonColor =
+ SkColorSetA(kHUDDefaultColor, 0xFF * 0.5);
// Radius of rounded corners for tabs.
// Must be be divisible by 3 to make kTabOverlayWidth integer.
diff --git a/ash/hud_display/hud_display.cc b/ash/hud_display/hud_display.cc
index 41640cb..f7f8b24 100644
--- a/ash/hud_display/hud_display.cc
+++ b/ash/hud_display/hud_display.cc
@@ -39,6 +39,7 @@
// Default HUDDisplayView height.
constexpr size_t kDefaultHUDGraphHeight = 300;
+constexpr size_t kDefaultHUDSettingHeight = 400;
// Top border + Header height + margin + graph height + bottom border..
constexpr int kHUDViewDefaultHeight =
@@ -70,6 +71,8 @@
return hud_display_->NonClientHitTest(point);
}
+ HUDDisplayView* GetHUDDisplayViewForTesting() { return hud_display_; }
+
private:
HUDDisplayView* hud_display_;
};
@@ -136,6 +139,11 @@
g_hud_widget = widget;
}
+// static
+bool HUDDisplayView::IsShown() {
+ return g_hud_widget;
+}
+
HUDDisplayView::HUDDisplayView() {
DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
@@ -191,6 +199,14 @@
// There is only one button.
void HUDDisplayView::OnSettingsToggle() {
+ gfx::Rect bounds = g_hud_widget->GetWindowBoundsInScreen();
+ constexpr int settings_height_addition =
+ kDefaultHUDSettingHeight - kDefaultHUDGraphHeight;
+ // Adjust window height.
+ bounds.set_height(bounds.height() + (settings_view_->GetVisible() ? -1 : 1) *
+ settings_height_addition);
+ g_hud_widget->SetBounds(bounds);
+
settings_view_->ToggleVisibility();
graphs_container_->SetVisible(!settings_view_->GetVisible());
}
@@ -206,6 +222,28 @@
->SetIsOverlayCandidate(g_hud_overlay_mode);
}
+// static
+HUDDisplayView* HUDDisplayView::GetForTesting() {
+ if (!g_hud_widget)
+ return nullptr;
+
+ HTClientView* client_view =
+ static_cast<HTClientView*>(g_hud_widget->client_view());
+
+ if (!client_view)
+ return nullptr;
+
+ return client_view->GetHUDDisplayViewForTesting(); // IN-TEST
+}
+
+HUDSettingsView* HUDDisplayView::GetSettingsViewForTesting() {
+ return settings_view_;
+}
+
+void HUDDisplayView::ToggleSettingsForTesting() {
+ OnSettingsToggle();
+}
+
int HUDDisplayView::NonClientHitTest(const gfx::Point& point) {
const View* view = GetEventHandlerForPoint(point);
if (!view)
diff --git a/ash/hud_display/hud_display.h b/ash/hud_display/hud_display.h
index 8dc3f3f..e929ea4b 100644
--- a/ash/hud_display/hud_display.h
+++ b/ash/hud_display/hud_display.h
@@ -5,6 +5,7 @@
#ifndef ASH_HUD_DISPLAY_HUD_DISPLAY_H_
#define ASH_HUD_DISPLAY_HUD_DISPLAY_H_
+#include "ash/ash_export.h"
#include "base/sequence_checker.h"
#include "ui/views/view.h"
@@ -33,6 +34,9 @@
// Creates/Destroys global singleton.
static void Toggle();
+ // True when HUD is shown.
+ static bool ASH_EXPORT IsShown();
+
// Called from ClientView. Responsible for moving widget when clicked outside
// of the children.
int NonClientHitTest(const gfx::Point& point);
@@ -49,6 +53,10 @@
// Changes HUD overlay flag.
void ToggleOverlay();
+ ASH_EXPORT static HUDDisplayView* GetForTesting();
+ ASH_EXPORT HUDSettingsView* GetSettingsViewForTesting();
+ ASH_EXPORT void ToggleSettingsForTesting();
+
private:
HUDHeaderView* header_view_ = nullptr; // not owned
GraphsContainerView* graphs_container_ = nullptr; // not owned
diff --git a/ash/hud_display/hud_header_view.cc b/ash/hud_display/hud_header_view.cc
index 6ce1307..27cf43e 100644
--- a/ash/hud_display/hud_header_view.cc
+++ b/ash/hud_display/hud_header_view.cc
@@ -186,9 +186,10 @@
gfx::Insets(kHUDInset, kHUDInset, 0, kHUDInset)));
// Add buttons and tab strip.
- header_buttons->AddChildView(
- std::make_unique<SettingsButton>(base::BindRepeating(
- &HUDDisplayView::OnSettingsToggle, base::Unretained(hud))));
+ header_buttons
+ ->AddChildView(std::make_unique<SettingsButton>(base::BindRepeating(
+ &HUDDisplayView::OnSettingsToggle, base::Unretained(hud))))
+ ->SetTooltipText(u"Trigger Ash HUD Settings");
tab_strip_ = header_buttons->AddChildView(std::make_unique<HUDTabStrip>(hud));
// Padding will take the rest of the header and draw bottom inner left
diff --git a/ash/hud_display/hud_settings_view.cc b/ash/hud_display/hud_settings_view.cc
index 1ae2042..672bda1 100644
--- a/ash/hud_display/hud_settings_view.cc
+++ b/ash/hud_display/hud_settings_view.cc
@@ -6,6 +6,7 @@
#include <string>
+#include "ash/hud_display/ash_tracing_handler.h"
#include "ash/hud_display/hud_display.h"
#include "ash/hud_display/hud_properties.h"
#include "ash/shell.h"
@@ -22,6 +23,7 @@
#include "ui/compositor/compositor.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/gfx/canvas.h"
+#include "ui/gfx/paint_throbber.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/button/label_button.h"
@@ -334,19 +336,72 @@
views::View::Layout();
}
-std::unique_ptr<views::LabelButton> CreateActionButton(
- views::Button::PressedCallback::Callback callback,
- const std::u16string& text) {
- auto button = std::make_unique<views::LabelButton>(callback, text);
- button->SetHorizontalAlignment(gfx::ALIGN_CENTER);
- button->SetEnabledTextColors(kHUDBackground);
- button->SetProperty(kHUDClickHandler, HTCLIENT);
- constexpr float kActionButtonCournerRadius = 2;
- button->SetBackground(views::CreateRoundedRectBackground(
- kHUDDefaultColor, kActionButtonCournerRadius));
- button->SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
- return button;
-}
+class HUDActionButton : public views::LabelButton {
+ public:
+ HUDActionButton(views::Button::PressedCallback::Callback callback,
+ const std::u16string& text)
+ : LabelButton(callback, text) {
+ SetHorizontalAlignment(gfx::ALIGN_CENTER);
+ SetEnabledTextColors(kHUDBackground);
+ SetProperty(kHUDClickHandler, HTCLIENT);
+ constexpr float kActionButtonCournerRadius = 2;
+ SetBackground(views::CreateRoundedRectBackground(
+ kHUDDefaultColor, kActionButtonCournerRadius));
+ SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
+ on_enabled_changed_subscription_ =
+ AddEnabledChangedCallback(base::BindRepeating(
+ &HUDActionButton::OnEnabedChanged, base::Unretained(this)));
+ }
+
+ HUDActionButton(const HUDActionButton&) = delete;
+ HUDActionButton& operator=(const HUDActionButton&) = delete;
+
+ ~HUDActionButton() override = default;
+
+ void PaintButtonContents(gfx::Canvas* canvas) override {
+ views::LabelButton::PaintButtonContents(canvas);
+ if (spinner_refresh_timer_.IsRunning()) {
+ base::Time now = base::Time::Now();
+ gfx::Rect spinner = GetContentsBounds();
+ int spinner_width = std::min(spinner.width(), spinner.height());
+ spinner.ClampToCenteredSize(gfx::Size(spinner_width, spinner_width));
+ gfx::PaintThrobberSpinning(canvas, spinner,
+ SkColorSetA(SK_ColorWHITE, 0xFF * (.5)),
+ (now - spinner_created_) / 8);
+ }
+ }
+
+ void DisableWithSpinner() {
+ DCHECK(!spinner_refresh_timer_.IsRunning());
+ SetEnabled(false);
+ constexpr base::TimeDelta interval = base::TimeDelta::FromSecondsD(0.5);
+ spinner_created_ = base::Time::Now();
+ spinner_refresh_timer_.Start(
+ FROM_HERE, interval,
+ base::BindRepeating(
+ [](views::View* button) { button->SchedulePaint(); },
+ base::Unretained(this)));
+ SchedulePaint();
+ }
+
+ void UpdateBackgroundColor() override {
+ if (GetVisualState() == STATE_DISABLED) {
+ GetBackground()->SetNativeControlColor(kHUDDisabledButtonColor);
+ } else {
+ GetBackground()->SetNativeControlColor(kHUDDefaultColor);
+ }
+ }
+
+ private:
+ void OnEnabedChanged() {
+ if (GetEnabled())
+ spinner_refresh_timer_.Stop();
+ }
+
+ base::CallbackListSubscription on_enabled_changed_subscription_;
+ base::Time spinner_created_;
+ base::RepeatingTimer spinner_refresh_timer_;
+};
} // anonymous namespace
@@ -444,14 +499,66 @@
views::BoxLayout::Orientation::kVertical))
->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStretch);
+
+ // Tracing controls.
+ constexpr int kTracingControlButtonMargin = 6;
+ views::View* tracing_controls = AddChildView(std::make_unique<views::View>());
+ tracing_controls
+ ->SetLayoutManager(std::make_unique<views::BoxLayout>(
+ views::BoxLayout::Orientation::kVertical))
+ ->set_cross_axis_alignment(
+ views::BoxLayout::CrossAxisAlignment::kStretch);
+
ui_devtools_controls->SetBorder(
views::CreateEmptyBorder(gfx::Insets(kUiDevToolsControlButtonMargin)));
ui_dev_tools_control_button_ =
- ui_devtools_controls->AddChildView(CreateActionButton(
+ ui_devtools_controls->AddChildView(std::make_unique<HUDActionButton>(
base::BindRepeating(&HUDSettingsView::OnEnableUiDevToolsButtonPressed,
base::Unretained(this)),
std::u16string()));
UpdateDevToolsControlButtonLabel();
+
+ tracing_controls->SetBorder(
+ views::CreateEmptyBorder(gfx::Insets(kTracingControlButtonMargin)));
+ tracing_control_button_ =
+ tracing_controls->AddChildView(std::make_unique<HUDActionButton>(
+ base::BindRepeating(&HUDSettingsView::OnEnableTracingButtonPressed,
+ base::Unretained(this)),
+ std::u16string()));
+
+ const int kLabelBorderWidth = 3;
+ tracing_status_message_ =
+ tracing_controls->AddChildView(std::make_unique<views::Label>(
+ std::u16string(), views::style::CONTEXT_LABEL));
+ tracing_status_message_->SetAutoColorReadabilityEnabled(false);
+ tracing_status_message_->SetEnabledColor(kHUDDefaultColor);
+ tracing_status_message_->SetBorder(views::CreateEmptyBorder(
+ gfx::Insets(/*vertical=*/0, /*horizontal=*/kLabelBorderWidth)));
+ tracing_status_message_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+
+ views::Label* pii_label = tracing_controls->AddChildView(std::make_unique<
+ views::Label>(
+ u"WARNING: Trace files may contain Personally Identifiable Information. "
+ u"You should use discretion when sharing your trace files.",
+ views::style::CONTEXT_LABEL));
+ pii_label->SetMultiLine(true);
+ pii_label->SetAutoColorReadabilityEnabled(false);
+ pii_label->SetEnabledColor(kHUDDefaultColor);
+ pii_label->SetBorder(views::CreateEmptyBorder(
+ gfx::Insets(/*vertical=*/0, /*horizontal=*/kLabelBorderWidth)));
+ pii_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+
+ UpdateTracingControlButton();
+
+ AshTracingManager::Get().AddObserver(this);
+}
+
+HUDSettingsView::~HUDSettingsView() {
+ AshTracingManager::Get().RemoveObserver(this);
+}
+
+void HUDSettingsView::OnTracingStatusChange() {
+ UpdateTracingControlButton();
}
void HUDSettingsView::OnEnableUiDevToolsButtonPressed(const ui::Event& event) {
@@ -473,8 +580,6 @@
}
}
-HUDSettingsView::~HUDSettingsView() = default;
-
void HUDSettingsView::ToggleVisibility() {
const bool is_shown = !GetVisible();
if (is_shown) {
@@ -485,6 +590,38 @@
SetVisible(is_shown);
}
+void HUDSettingsView::OnEnableTracingButtonPressed(const ui::Event& event) {
+ ToggleTracing();
+}
+
+void HUDSettingsView::ToggleTracingForTesting() {
+ ToggleTracing();
+}
+
+void HUDSettingsView::ToggleTracing() {
+ AshTracingManager& manager = AshTracingManager::Get();
+ tracing_control_button_->DisableWithSpinner();
+ if (manager.IsTracingStarted()) {
+ manager.Stop();
+ } else {
+ manager.Start();
+ }
+}
+
+void HUDSettingsView::UpdateTracingControlButton() {
+ AshTracingManager& manager = AshTracingManager::Get();
+ if (!manager.IsBusy())
+ tracing_control_button_->SetEnabled(true);
+
+ tracing_status_message_->SetText(
+ base::ASCIIToUTF16(manager.GetStatusMessage()));
+ if (manager.IsTracingStarted()) {
+ tracing_control_button_->SetText(u"Stop tracing.");
+ } else {
+ tracing_control_button_->SetText(u"Start tracing.");
+ }
+}
+
} // namespace hud_display
} // namespace ash
diff --git a/ash/hud_display/hud_settings_view.h b/ash/hud_display/hud_settings_view.h
index 8a900740..0734234 100644
--- a/ash/hud_display/hud_settings_view.h
+++ b/ash/hud_display/hud_settings_view.h
@@ -8,7 +8,9 @@
#include <memory>
#include <vector>
+#include "ash/hud_display/ash_tracing_manager.h"
#include "ash/hud_display/hud_constants.h"
+#include "base/memory/weak_ptr.h"
#include "ui/views/view.h"
namespace ui {
@@ -16,6 +18,7 @@
}
namespace views {
+class Label;
class LabelButton;
}
@@ -25,7 +28,11 @@
class HUDCheckboxHandler;
class HUDDisplayView;
-class HUDSettingsView : public views::View {
+namespace {
+class HUDActionButton;
+}
+
+class HUDSettingsView : public AshTracingManager::Observer, public views::View {
public:
METADATA_HEADER(HUDSettingsView);
@@ -35,20 +42,39 @@
HUDSettingsView(const HUDSettingsView&) = delete;
HUDSettingsView& operator=(const HUDSettingsView&) = delete;
+ // AshTracingManager::Observer
+ void OnTracingStatusChange() override;
+
// Shows/hides the view.
void ToggleVisibility();
// Creates Ui Dev Tools.
void OnEnableUiDevToolsButtonPressed(const ui::Event& event);
+ // Starts tracing.
+ void OnEnableTracingButtonPressed(const ui::Event& event);
+
+ ASH_EXPORT void ToggleTracingForTesting();
+
private:
+ // Starts/Stops tracing.
+ void ToggleTracing();
+
// Replace "Create Ui Dev Tools" button label with "DevTools running".
void UpdateDevToolsControlButtonLabel();
+ // Switches between "Start tracing" and "Stop tracing" button labels.
+ void UpdateTracingControlButton();
+
std::vector<std::unique_ptr<HUDCheckboxHandler>> checkbox_handlers_;
// Container for "Create Ui Dev Tools" button or "DevTools running" label.
views::LabelButton* ui_dev_tools_control_button_ = nullptr;
+
+ HUDActionButton* tracing_control_button_ = nullptr;
+ views::Label* tracing_status_message_ = nullptr;
+
+ base::WeakPtrFactory<HUDSettingsView> weak_factory_{this};
};
} // namespace hud_display
diff --git a/ash/public/cpp/test/shell_test_api.h b/ash/public/cpp/test/shell_test_api.h
index 2f8a01d..85e039df 100644
--- a/ash/public/cpp/test/shell_test_api.h
+++ b/ash/public/cpp/test/shell_test_api.h
@@ -21,6 +21,10 @@
class DisplayManager;
}
+namespace ui {
+class Accelerator;
+}
+
namespace ash {
enum class AppListViewState;
class DragDropController;
@@ -126,6 +130,13 @@
// shown.
bool IsContextMenuShown() const;
+ // Sends accelerator directly to AcceleratorController.
+ bool IsActionForAcceleratorEnabled(const ui::Accelerator& accelerator) const;
+ bool PressAccelerator(const ui::Accelerator& accelerator);
+
+ // Returns true when Ash HUD is shown.
+ bool IsHUDShown();
+
private:
Shell* shell_; // not owned
diff --git a/ash/shell_delegate.h b/ash/shell_delegate.h
index 6d63a36..f923691 100644
--- a/ash/shell_delegate.h
+++ b/ash/shell_delegate.h
@@ -10,6 +10,7 @@
#include "ash/ash_export.h"
#include "base/callback.h"
+#include "base/files/file_path.h"
#include "chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom-forward.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "services/device/public/mojom/bluetooth_system.mojom-forward.h"
@@ -115,6 +116,13 @@
virtual void StartUiDevTools() {}
virtual void StopUiDevTools() {}
virtual int GetUiDevToolsPort() const;
+
+ // Returns true if Chrome was started with --disable-logging-redirect option.
+ virtual bool IsLoggingRedirectDisabled() const = 0;
+
+ // Returns empty path is user session has not started yet, or path to the
+ // primary user Downloads folder if user has already logged in.
+ virtual base::FilePath GetPrimaryUserDownloadsFolder() const = 0;
};
} // namespace ash
diff --git a/ash/shell_test_api.cc b/ash/shell_test_api.cc
index 87a3760a..8a0e1e4 100644
--- a/ash/shell_test_api.cc
+++ b/ash/shell_test_api.cc
@@ -8,9 +8,11 @@
#include <utility>
#include "ash/accelerators/accelerator_commands.h"
+#include "ash/accelerators/accelerator_controller_impl.h"
#include "ash/accelerometer/accelerometer_reader.h"
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/app_list/views/app_list_view.h"
+#include "ash/hud_display/hud_display.h"
#include "ash/keyboard/keyboard_controller_impl.h"
#include "ash/public/cpp/autotest_private_api_utils.h"
#include "ash/public/cpp/tablet_mode_observer.h"
@@ -263,4 +265,19 @@
return Shell::GetPrimaryRootWindowController()->IsContextMenuShown();
}
+bool ShellTestApi::IsActionForAcceleratorEnabled(
+ const ui::Accelerator& accelerator) const {
+ return Shell::Get()->accelerator_controller()->IsActionForAcceleratorEnabled(
+ accelerator);
+}
+
+bool ShellTestApi::PressAccelerator(const ui::Accelerator& accelerator) {
+ return Shell::Get()->accelerator_controller()->AcceleratorPressed(
+ accelerator);
+}
+
+bool ShellTestApi::IsHUDShown() {
+ return hud_display::HUDDisplayView::IsShown();
+}
+
} // namespace ash
diff --git a/ash/test_shell_delegate.cc b/ash/test_shell_delegate.cc
index a508bd2..074690b 100644
--- a/ash/test_shell_delegate.cc
+++ b/ash/test_shell_delegate.cc
@@ -86,4 +86,12 @@
session_restore_in_progress_ = in_progress;
}
+bool TestShellDelegate::IsLoggingRedirectDisabled() const {
+ return false;
+}
+
+base::FilePath TestShellDelegate::GetPrimaryUserDownloadsFolder() const {
+ return base::FilePath();
+}
+
} // namespace ash
diff --git a/ash/test_shell_delegate.h b/ash/test_shell_delegate.h
index b288e27..638bc57 100644
--- a/ash/test_shell_delegate.h
+++ b/ash/test_shell_delegate.h
@@ -52,6 +52,8 @@
void SetCanGoBack(bool can_go_back);
void SetShouldWaitForTouchAck(bool should_wait_for_touch_ack);
void SetSessionRestoreInProgress(bool in_progress);
+ bool IsLoggingRedirectDisabled() const override;
+ base::FilePath GetPrimaryUserDownloadsFolder() const override;
private:
// True if the current top window can go back.