summaryrefslogtreecommitdiff
path: root/chromium/content/common/android
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-12 14:27:29 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-13 09:35:20 +0000
commitc30a6232df03e1efbd9f3b226777b07e087a1122 (patch)
treee992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/content/common/android
parent7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff)
downloadqtwebengine-chromium-85-based.tar.gz
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/content/common/android')
-rw-r--r--chromium/content/common/android/cpu_time_metrics.cc400
-rw-r--r--chromium/content/common/android/cpu_time_metrics.h28
-rw-r--r--chromium/content/common/android/cpu_time_metrics_unittest.cc66
3 files changed, 494 insertions, 0 deletions
diff --git a/chromium/content/common/android/cpu_time_metrics.cc b/chromium/content/common/android/cpu_time_metrics.cc
new file mode 100644
index 00000000000..9c5490d76a0
--- /dev/null
+++ b/chromium/content/common/android/cpu_time_metrics.cc
@@ -0,0 +1,400 @@
+// Copyright 2020 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/common/android/cpu_time_metrics.h"
+
+#include <stdint.h>
+
+#include <atomic>
+#include <memory>
+
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/containers/flat_map.h"
+#include "base/lazy_instance.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/no_destructor.h"
+#include "base/process/process_metrics.h"
+#include "base/sequence_checker.h"
+#include "base/strings/pattern.h"
+#include "base/strings/string_util.h"
+#include "base/task/post_task.h"
+#include "base/task/task_observer.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread_id_name_manager.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/process_type.h"
+
+namespace content {
+namespace {
+
+// Histogram macros expect an enum class with kMaxValue. Because
+// content::ProcessType cannot be migrated to this style at the moment, we
+// specify a separate version here. Keep in sync with content::ProcessType.
+// TODO(eseckler): Replace with content::ProcessType after its migration.
+enum class ProcessTypeForUma {
+ kUnknown = 1,
+ kBrowser,
+ kRenderer,
+ kPluginDeprecated,
+ kWorkerDeprecated,
+ kUtility,
+ kZygote,
+ kSandboxHelper,
+ kGpu,
+ kPpapiPlugin,
+ kPpapiBroker,
+ kMaxValue = kPpapiBroker,
+};
+
+static_assert(static_cast<int>(ProcessTypeForUma::kMaxValue) ==
+ PROCESS_TYPE_PPAPI_BROKER,
+ "ProcessTypeForUma and CurrentProcessType() require updating");
+
+ProcessTypeForUma CurrentProcessType() {
+ std::string process_type =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kProcessType);
+ if (process_type.empty())
+ return ProcessTypeForUma::kBrowser;
+ if (process_type == switches::kRendererProcess)
+ return ProcessTypeForUma::kRenderer;
+ if (process_type == switches::kUtilityProcess)
+ return ProcessTypeForUma::kUtility;
+ if (process_type == switches::kSandboxIPCProcess)
+ return ProcessTypeForUma::kSandboxHelper;
+ if (process_type == switches::kGpuProcess)
+ return ProcessTypeForUma::kGpu;
+ if (process_type == switches::kPpapiPluginProcess)
+ return ProcessTypeForUma::kPpapiPlugin;
+ if (process_type == switches::kPpapiBrokerProcess)
+ return ProcessTypeForUma::kPpapiBroker;
+ NOTREACHED() << "Unexpected process type: " << process_type;
+ return ProcessTypeForUma::kUnknown;
+}
+
+const char* GetPerThreadHistogramNameForProcessType(ProcessTypeForUma type) {
+ switch (type) {
+ case ProcessTypeForUma::kBrowser:
+ return "Power.CpuTimeSecondsPerThreadType.Browser";
+ case ProcessTypeForUma::kRenderer:
+ return "Power.CpuTimeSecondsPerThreadType.Renderer";
+ case ProcessTypeForUma::kGpu:
+ return "Power.CpuTimeSecondsPerThreadType.GPU";
+ default:
+ return "Power.CpuTimeSecondsPerThreadType.Other";
+ }
+}
+
+// Keep in sync with CpuTimeMetricsThreadType in
+// //tools/metrics/histograms/enums.xml.
+enum class CpuTimeMetricsThreadType {
+ kUnattributedThread = 0,
+ kOtherThread,
+ kMainThread,
+ kIOThread,
+ kThreadPoolBackgroundWorkerThread,
+ kThreadPoolForegroundWorkerThread,
+ kThreadPoolServiceThread,
+ kCompositorThread,
+ kCompositorTileWorkerThread,
+ kVizCompositorThread,
+ kRendererUnspecifiedWorkerThread,
+ kRendererDedicatedWorkerThread,
+ kRendererSharedWorkerThread,
+ kRendererAnimationAndPaintWorkletThread,
+ kRendererServiceWorkerThread,
+ kRendererAudioWorkletThread,
+ kRendererFileThread,
+ kRendererDatabaseThread,
+ kRendererOfflineAudioRenderThread,
+ kRendererReverbConvolutionBackgroundThread,
+ kRendererHRTFDatabaseLoaderThread,
+ kRendererAudioEncoderThread,
+ kRendererVideoEncoderThread,
+ kMemoryInfraThread,
+ kSamplingProfilerThread,
+ kNetworkServiceThread,
+ kAudioThread,
+ kInProcessUtilityThread,
+ kInProcessRendererThread,
+ kInProcessGpuThread,
+ kMaxValue = kInProcessGpuThread,
+};
+
+CpuTimeMetricsThreadType GetThreadTypeFromName(const char* const thread_name) {
+ if (!thread_name)
+ return CpuTimeMetricsThreadType::kOtherThread;
+
+ if (base::MatchPattern(thread_name, "Cr*Main")) {
+ return CpuTimeMetricsThreadType::kMainThread;
+ } else if (base::MatchPattern(thread_name, "Chrome*IOThread")) {
+ return CpuTimeMetricsThreadType::kIOThread;
+ } else if (base::MatchPattern(thread_name, "ThreadPool*Foreground*")) {
+ return CpuTimeMetricsThreadType::kThreadPoolForegroundWorkerThread;
+ } else if (base::MatchPattern(thread_name, "ThreadPool*Background*")) {
+ return CpuTimeMetricsThreadType::kThreadPoolBackgroundWorkerThread;
+ } else if (base::MatchPattern(thread_name, "ThreadPoolService*")) {
+ return CpuTimeMetricsThreadType::kThreadPoolServiceThread;
+ } else if (base::MatchPattern(thread_name, "Compositor")) {
+ return CpuTimeMetricsThreadType::kCompositorThread;
+ } else if (base::MatchPattern(thread_name, "CompositorTileWorker*")) {
+ return CpuTimeMetricsThreadType::kCompositorTileWorkerThread;
+ } else if (base::MatchPattern(thread_name, "VizCompositor*")) {
+ return CpuTimeMetricsThreadType::kVizCompositorThread;
+ } else if (base::MatchPattern(thread_name, "unspecified worker*")) {
+ return CpuTimeMetricsThreadType::kRendererUnspecifiedWorkerThread;
+ } else if (base::MatchPattern(thread_name, "DedicatedWorker*")) {
+ return CpuTimeMetricsThreadType::kRendererDedicatedWorkerThread;
+ } else if (base::MatchPattern(thread_name, "SharedWorker*")) {
+ return CpuTimeMetricsThreadType::kRendererSharedWorkerThread;
+ } else if (base::MatchPattern(thread_name, "AnimationWorklet*")) {
+ return CpuTimeMetricsThreadType::kRendererAnimationAndPaintWorkletThread;
+ } else if (base::MatchPattern(thread_name, "ServiceWorker*")) {
+ return CpuTimeMetricsThreadType::kRendererServiceWorkerThread;
+ } else if (base::MatchPattern(thread_name, "AudioWorklet*")) {
+ return CpuTimeMetricsThreadType::kRendererAudioWorkletThread;
+ } else if (base::MatchPattern(thread_name, "File thread")) {
+ return CpuTimeMetricsThreadType::kRendererFileThread;
+ } else if (base::MatchPattern(thread_name, "Database thread")) {
+ return CpuTimeMetricsThreadType::kRendererDatabaseThread;
+ } else if (base::MatchPattern(thread_name, "OfflineAudioRender*")) {
+ return CpuTimeMetricsThreadType::kRendererOfflineAudioRenderThread;
+ } else if (base::MatchPattern(thread_name, "Reverb convolution*")) {
+ return CpuTimeMetricsThreadType::kRendererReverbConvolutionBackgroundThread;
+ } else if (base::MatchPattern(thread_name, "HRTF*")) {
+ return CpuTimeMetricsThreadType::kRendererHRTFDatabaseLoaderThread;
+ } else if (base::MatchPattern(thread_name, "Audio encoder*")) {
+ return CpuTimeMetricsThreadType::kRendererAudioEncoderThread;
+ } else if (base::MatchPattern(thread_name, "Video encoder*")) {
+ return CpuTimeMetricsThreadType::kRendererVideoEncoderThread;
+ } else if (base::MatchPattern(thread_name, "MemoryInfra")) {
+ return CpuTimeMetricsThreadType::kMemoryInfraThread;
+ } else if (base::MatchPattern(thread_name, "StackSamplingProfiler")) {
+ return CpuTimeMetricsThreadType::kSamplingProfilerThread;
+ } else if (base::MatchPattern(thread_name, "NetworkService")) {
+ return CpuTimeMetricsThreadType::kNetworkServiceThread;
+ } else if (base::MatchPattern(thread_name, "AudioThread")) {
+ return CpuTimeMetricsThreadType::kAudioThread;
+ } else if (base::MatchPattern(thread_name, "Chrome_InProcUtilityThread")) {
+ return CpuTimeMetricsThreadType::kInProcessUtilityThread;
+ } else if (base::MatchPattern(thread_name, "Chrome_InProcRendererThread")) {
+ return CpuTimeMetricsThreadType::kInProcessRendererThread;
+ } else if (base::MatchPattern(thread_name, "Chrome_InProcGpuThread")) {
+ return CpuTimeMetricsThreadType::kInProcessGpuThread;
+ }
+
+ // TODO(eseckler): Also break out Android's RenderThread here somehow?
+
+ return CpuTimeMetricsThreadType::kOtherThread;
+}
+
+// Samples the process's CPU time after a specific number of task were executed
+// on the current thread (process main). The number of tasks is a crude proxy
+// for CPU activity within this process. We sample more frequently when the
+// process is more active, thus ensuring we lose little CPU time attribution
+// when the process is terminated, even after it was very active.
+class ProcessCpuTimeTaskObserver : public base::TaskObserver {
+ public:
+ static ProcessCpuTimeTaskObserver* GetInstance() {
+ static base::NoDestructor<ProcessCpuTimeTaskObserver> instance;
+ return instance.get();
+ }
+
+ ProcessCpuTimeTaskObserver()
+ : task_runner_(base::CreateSequencedTaskRunner(
+ {base::ThreadPool(), base::TaskPriority::BEST_EFFORT,
+ // TODO(eseckler): Consider hooking into process shutdown on
+ // desktop to reduce metric data loss.
+ base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
+ process_metrics_(base::ProcessMetrics::CreateCurrentProcessMetrics()),
+ process_type_(CurrentProcessType()),
+ // The observer is created on the main thread of the process.
+ main_thread_id_(base::PlatformThread::CurrentId()) {
+ // Browser and GPU processes have a longer lifetime (don't disappear between
+ // navigations), and typically execute a large number of small main-thread
+ // tasks. For these processes, choose a higher reporting interval.
+ if (process_type_ == ProcessTypeForUma::kBrowser ||
+ process_type_ == ProcessTypeForUma::kGpu) {
+ reporting_interval_ = kReportAfterEveryNTasksPersistentProcess;
+ } else {
+ reporting_interval_ = kReportAfterEveryNTasksOtherProcess;
+ }
+ DETACH_FROM_SEQUENCE(thread_pool_);
+ }
+
+ // base::TaskObserver implementation:
+ void WillProcessTask(const base::PendingTask& pending_task,
+ bool was_blocked_or_low_priority) override {}
+
+ void DidProcessTask(const base::PendingTask& pending_task) override {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_thread_);
+ // We perform the collection from a background thread. Only schedule another
+ // one after a reasonably large amount of work was executed after the last
+ // collection completed. std::memory_order_relaxed because we only care that
+ // we pick up the change back by the posted task eventually.
+ if (collection_in_progress_.load(std::memory_order_relaxed))
+ return;
+ task_counter_++;
+ if (task_counter_ == reporting_interval_) {
+ // PostTask() applies a barrier, so this will be applied before the thread
+ // pool task executes and sets |collection_in_progress_| back to false.
+ collection_in_progress_.store(true, std::memory_order_relaxed);
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &ProcessCpuTimeTaskObserver::CollectAndReportCpuTimeOnThreadPool,
+ base::Unretained(this)));
+ task_counter_ = 0;
+ }
+ }
+
+ void CollectAndReportCpuTimeOnThreadPool() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(thread_pool_);
+
+ // This might overflow. We only care that it is different for each cycle.
+ current_cycle_++;
+
+ // GetCumulativeCPUUsage() may return a negative value if sampling failed.
+ base::TimeDelta cumulative_cpu_time =
+ process_metrics_->GetCumulativeCPUUsage();
+ base::TimeDelta cpu_time_delta = cumulative_cpu_time - reported_cpu_time_;
+ if (cpu_time_delta > base::TimeDelta()) {
+ UMA_HISTOGRAM_SCALED_ENUMERATION(
+ "Power.CpuTimeSecondsPerProcessType", process_type_,
+ cpu_time_delta.InMicroseconds(), base::Time::kMicrosecondsPerSecond);
+ reported_cpu_time_ = cumulative_cpu_time;
+ }
+
+ // Also report a breakdown by thread type.
+ base::TimeDelta unattributed_delta = cpu_time_delta;
+ if (process_metrics_->GetCumulativeCPUUsagePerThread(
+ cumulative_thread_times_)) {
+ for (const auto& entry : cumulative_thread_times_) {
+ base::PlatformThreadId tid = entry.first;
+ base::TimeDelta cumulative_time = entry.second;
+
+ auto it_and_inserted = thread_details_.emplace(
+ tid, ThreadDetails{base::TimeDelta(), current_cycle_});
+ ThreadDetails* thread_details = &it_and_inserted.first->second;
+
+ if (it_and_inserted.second) {
+ // New thread.
+ thread_details->type = GuessThreadType(tid);
+ }
+
+ thread_details->last_updated_cycle = current_cycle_;
+
+ // Skip negative or null values, might be a transient collection error.
+ if (cumulative_time <= base::TimeDelta())
+ continue;
+
+ if (cumulative_time < thread_details->reported_cpu_time) {
+ // PlatformThreadId was likely reused, reset the details.
+ thread_details->reported_cpu_time = base::TimeDelta();
+ thread_details->type = GuessThreadType(tid);
+ }
+
+ base::TimeDelta thread_delta =
+ cumulative_time - thread_details->reported_cpu_time;
+ unattributed_delta -= thread_delta;
+
+ ReportThreadCpuTimeDelta(thread_details->type, thread_delta);
+ thread_details->reported_cpu_time = cumulative_time;
+ }
+
+ // Erase tracking for threads that have disappeared, as their
+ // PlatformThreadId may be reused later.
+ for (auto it = thread_details_.begin(); it != thread_details_.end();) {
+ if (it->second.last_updated_cycle == current_cycle_) {
+ it++;
+ } else {
+ it = thread_details_.erase(it);
+ }
+ }
+ }
+
+ // Report the difference of the process's total CPU time and all thread's
+ // CPU time as unattributed time (e.g. time consumed by threads that died).
+ if (unattributed_delta > base::TimeDelta()) {
+ ReportThreadCpuTimeDelta(CpuTimeMetricsThreadType::kUnattributedThread,
+ unattributed_delta);
+ }
+
+ collection_in_progress_.store(false, std::memory_order_relaxed);
+ }
+
+ private:
+ struct ThreadDetails {
+ base::TimeDelta reported_cpu_time;
+ uint32_t last_updated_cycle = 0;
+ CpuTimeMetricsThreadType type = CpuTimeMetricsThreadType::kOtherThread;
+ };
+
+ void ReportThreadCpuTimeDelta(CpuTimeMetricsThreadType type,
+ base::TimeDelta cpu_time_delta) {
+ // Histogram name cannot change after being used once. That's ok since this
+ // only depends on the process type, which also doesn't change.
+ static const char* histogram_name =
+ GetPerThreadHistogramNameForProcessType(process_type_);
+ UMA_HISTOGRAM_SCALED_ENUMERATION(histogram_name, type,
+ cpu_time_delta.InMicroseconds(),
+ base::Time::kMicrosecondsPerSecond);
+ }
+
+ CpuTimeMetricsThreadType GuessThreadType(base::PlatformThreadId tid) {
+ // Match the main thread by TID, so that this also works for WebView, where
+ // the main thread can have an arbitrary name.
+ if (tid == main_thread_id_)
+ return CpuTimeMetricsThreadType::kMainThread;
+ const char* name = base::ThreadIdNameManager::GetInstance()->GetName(tid);
+ return GetThreadTypeFromName(name);
+ }
+
+ // Sample CPU time after a certain number of main-thread task to balance
+ // overhead of sampling and loss at process termination.
+ static constexpr int kReportAfterEveryNTasksPersistentProcess = 500;
+ static constexpr int kReportAfterEveryNTasksOtherProcess = 100;
+
+ // Accessed on main thread.
+ SEQUENCE_CHECKER(main_thread_);
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+ int task_counter_ = 0;
+ int reporting_interval_ = 0; // set in constructor.
+
+ // Accessed on |task_runner_|.
+ SEQUENCE_CHECKER(thread_pool_);
+ uint32_t current_cycle_ = 0;
+ std::unique_ptr<base::ProcessMetrics> process_metrics_;
+ ProcessTypeForUma process_type_;
+ base::PlatformThreadId main_thread_id_;
+ base::TimeDelta reported_cpu_time_;
+ // Stored as instance variable to avoid allocation churn.
+ base::ProcessMetrics::CPUUsagePerThread cumulative_thread_times_;
+ base::flat_map<base::PlatformThreadId, ThreadDetails> thread_details_;
+
+ // Accessed on both sequences.
+ std::atomic<bool> collection_in_progress_;
+};
+
+} // namespace
+
+void SetupCpuTimeMetrics() {
+ // May be called multiple times for in-process renderer/utility/GPU processes.
+ static bool did_setup = false;
+ if (did_setup)
+ return;
+ base::MessageLoopCurrent::Get()->AddTaskObserver(
+ ProcessCpuTimeTaskObserver::GetInstance());
+ did_setup = true;
+}
+
+void SampleCpuTimeMetricsForTesting() {
+ ProcessCpuTimeTaskObserver::GetInstance()
+ ->CollectAndReportCpuTimeOnThreadPool();
+}
+
+} // namespace content
diff --git a/chromium/content/common/android/cpu_time_metrics.h b/chromium/content/common/android/cpu_time_metrics.h
new file mode 100644
index 00000000000..38c4c2a750c
--- /dev/null
+++ b/chromium/content/common/android/cpu_time_metrics.h
@@ -0,0 +1,28 @@
+// Copyright 2020 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 CONTENT_COMMON_ANDROID_CPU_TIME_METRICS_H_
+#define CONTENT_COMMON_ANDROID_CPU_TIME_METRICS_H_
+
+#include "content/common/content_export.h"
+
+namespace content {
+
+// Sets up periodic collection/reporting of the process's CPU time. Should be
+// called on the process's main thread.
+//
+// The current process's CPU time usage is recorded periodically and reported it
+// into UMA histograms. The histogram data can later be used to approximate the
+// power consumption / efficiency of the app. Currently only supports Android,
+// where the sandbox allows isolated processes to read from /proc/self/stats.
+CONTENT_EXPORT void SetupCpuTimeMetrics();
+
+// Sample and report the CPU time UMA metrics immediately on the current thread.
+// Beware: Should only be used for testing and never in combination with
+// SetupCpuTimeMetrics().
+CONTENT_EXPORT void SampleCpuTimeMetricsForTesting();
+
+} // namespace content
+
+#endif // CONTENT_COMMON_ANDROID_CPU_TIME_METRICS_H_
diff --git a/chromium/content/common/android/cpu_time_metrics_unittest.cc b/chromium/content/common/android/cpu_time_metrics_unittest.cc
new file mode 100644
index 00000000000..8e383d42198
--- /dev/null
+++ b/chromium/content/common/android/cpu_time_metrics_unittest.cc
@@ -0,0 +1,66 @@
+// Copyright 2020 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/common/android/cpu_time_metrics.h"
+
+#include "base/synchronization/waitable_event.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+void WorkForOneCpuSec(base::WaitableEvent* event) {
+ auto initial_ticks = base::ThreadTicks::Now();
+ while (!event->IsSignaled()) {
+ if (base::ThreadTicks::Now() >
+ initial_ticks + base::TimeDelta::FromSeconds(1)) {
+ event->Signal();
+ }
+ }
+}
+
+TEST(CpuTimeMetricsTest, RecordsMetrics) {
+ base::test::TaskEnvironment task_environment;
+ base::HistogramTester histograms;
+ base::Thread thread1("StackSamplingProfiler");
+
+ thread1.StartAndWaitForTesting();
+ ASSERT_TRUE(thread1.IsRunning());
+
+ base::WaitableEvent event;
+
+ thread1.task_runner()->PostTask(
+ FROM_HERE, BindOnce(&WorkForOneCpuSec, base::Unretained(&event)));
+
+ // Wait until the thread has consumed one second of CPU time.
+ event.Wait();
+
+ // Update current metrics.
+ SampleCpuTimeMetricsForTesting();
+
+ // The test process has no process-type command line flag, so is recognized as
+ // the browser process. The thread created above is named like a sampling
+ // profiler thread.
+ static constexpr int kBrowserProcessBucket = 2;
+ static constexpr int kSamplingProfilerThreadBucket = 24;
+
+ // Expect that the CPU second spent by the thread above is represented in the
+ // metrics.
+ int browser_cpu_seconds = histograms.GetBucketCount(
+ "Power.CpuTimeSecondsPerProcessType", kBrowserProcessBucket);
+ EXPECT_GE(browser_cpu_seconds, 1);
+
+ int thread_cpu_seconds =
+ histograms.GetBucketCount("Power.CpuTimeSecondsPerThreadType.Browser",
+ kSamplingProfilerThreadBucket);
+ EXPECT_GE(thread_cpu_seconds, 1);
+
+ thread1.Stop();
+}
+
+} // namespace
+} // namespace content \ No newline at end of file