Commit 384f953d authored by Eric Seckler's avatar Eric Seckler Committed by Commit Bot

Reland "Reland "base: Add API to restrict thread affinity to little cores on Android""

This is a reland of 50136be2

Disabling the test on non-Android. We only care about this on Android at
the moment. linux-trusty-rel also seems to fail on sched_setaffinity,
likely a limitation of the host.

Original change's description:
> Reland "base: Add API to restrict thread affinity to little cores on Android"
>
> This is a reland of 027aa92e
>
> Relanding but disabling test on CrOS devices (some of our CrOS devices
> seem to have asymmetric CPUs that aren't covered by the test).
>
> Original change's description:
> > base: Add API to restrict thread affinity to little cores on Android
> >
> > Adds functions that facilitate setting the CPU affinity of a given
> > thread or process to all little cores on the system.
> >
> > We intend to use this API to run a power experiment.
> >
> > Bug: 1111789
> > Change-Id: I6c3d32c2338e9f00464e8b6a8c96af93658a3ae2
> > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2335176
> > Commit-Queue: Eric Seckler <eseckler@chromium.org>
> > Reviewed-by: Daniel Cheng <dcheng@chromium.org>
> > Reviewed-by: Sami Kyöstilä <skyostil@chromium.org>
> > Cr-Commit-Position: refs/heads/master@{#794998}
>
> TBR: dcheng@chromium.org
> Bug: 1111789
> Change-Id: I59315ec0e9a1727986278a04fa5bde409143dfb5
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2339416
> Reviewed-by: Eric Seckler <eseckler@chromium.org>
> Reviewed-by: Sami Kyöstilä <skyostil@chromium.org>
> Auto-Submit: Eric Seckler <eseckler@chromium.org>
> Commit-Queue: Sami Kyöstilä <skyostil@chromium.org>
> Commit-Queue: Eric Seckler <eseckler@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#795126}

TBR: dcheng@chromium.org
Bug: 1111789
Change-Id: I0c9770f2e0c7d5b8caba9432e55c5f3ebe198322
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2339342Reviewed-by: default avatarEric Seckler <eseckler@chromium.org>
Reviewed-by: default avatarSami Kyöstilä <skyostil@chromium.org>
Auto-Submit: Eric Seckler <eseckler@chromium.org>
Commit-Queue: Sami Kyöstilä <skyostil@chromium.org>
Cr-Commit-Position: refs/heads/master@{#795396}
parent f94cdee7
...@@ -886,6 +886,8 @@ jumbo_component("base") { ...@@ -886,6 +886,8 @@ jumbo_component("base") {
if (!is_nacl && !is_mac && !is_ios) { if (!is_nacl && !is_mac && !is_ios) {
sources += [ sources += [
"cpu_affinity_posix.cc",
"cpu_affinity_posix.h",
"profiler/stack_copier_signal.cc", "profiler/stack_copier_signal.cc",
"profiler/stack_copier_signal.h", "profiler/stack_copier_signal.h",
"profiler/stack_sampler_posix.cc", "profiler/stack_sampler_posix.cc",
...@@ -3055,7 +3057,10 @@ test("base_unittests") { ...@@ -3055,7 +3057,10 @@ test("base_unittests") {
"task/thread_pool/task_tracker_posix_unittest.cc", "task/thread_pool/task_tracker_posix_unittest.cc",
] ]
if (!is_nacl && !is_mac && !is_ios) { if (!is_nacl && !is_mac && !is_ios) {
sources += [ "profiler/stack_copier_signal_unittest.cc" ] sources += [
"cpu_affinity_posix_unittest.cc",
"profiler/stack_copier_signal_unittest.cc",
]
} }
} }
......
// 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 "base/cpu_affinity_posix.h"
#include <sched.h>
#include "base/cpu.h"
#include "base/process/internal_linux.h"
namespace base {
bool SetThreadCpuAffinityMode(PlatformThreadId thread_id,
CpuAffinityMode affinity) {
static const cpu_set_t kAllCores = []() {
cpu_set_t set;
memset(&set, 0xff, sizeof(set));
return set;
}();
static const cpu_set_t kLittleCores = []() {
std::vector<CPU::CoreType> core_types = CPU::GuessCoreTypes();
if (core_types.empty())
return kAllCores;
cpu_set_t set;
CPU_ZERO(&set);
for (size_t core_index = 0; core_index < core_types.size(); core_index++) {
switch (core_types[core_index]) {
case CPU::CoreType::kUnknown:
case CPU::CoreType::kOther:
case CPU::CoreType::kSymmetric:
// In the presence of an unknown core type or symmetric architecture,
// fall back to allowing all cores.
return kAllCores;
case CPU::CoreType::kBigLittle_Little:
case CPU::CoreType::kBigLittleBigger_Little:
CPU_SET(core_index, &set);
break;
case CPU::CoreType::kBigLittle_Big:
case CPU::CoreType::kBigLittleBigger_Big:
case CPU::CoreType::kBigLittleBigger_Bigger:
break;
}
}
return set;
}();
int result = 0;
switch (affinity) {
case CpuAffinityMode::kDefault:
result = sched_setaffinity(thread_id, sizeof(kAllCores), &kAllCores);
break;
case CpuAffinityMode::kLittleCoresOnly:
result =
sched_setaffinity(thread_id, sizeof(kLittleCores), &kLittleCores);
break;
}
return result == 0;
}
bool SetProcessCpuAffinityMode(ProcessHandle process_handle,
CpuAffinityMode affinity) {
bool any_threads = false;
bool result = true;
internal::ForEachProcessTask(
process_handle, [&any_threads, &result, affinity](
PlatformThreadId tid, const FilePath& /*task_path*/) {
any_threads = true;
result &= SetThreadCpuAffinityMode(tid, affinity);
});
return any_threads && result;
}
} // namespace base
// 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 BASE_CPU_AFFINITY_POSIX_H_
#define BASE_CPU_AFFINITY_POSIX_H_
#include "base/process/process_handle.h"
#include "base/threading/platform_thread.h"
namespace base {
enum class BASE_EXPORT CpuAffinityMode {
// No restrictions on affinity.
kDefault,
// Restrict execution to LITTLE cores only. Only has an effect on platforms
// where we detect presence of big.LITTLE-like CPU architectures.
kLittleCoresOnly
};
// Sets or clears restrictions on the CPU affinity of the specified thread.
// Returns false if updating the affinity failed.
BASE_EXPORT bool SetThreadCpuAffinityMode(PlatformThreadId thread_id,
CpuAffinityMode affinity);
// Like SetThreadAffinityMode, but affects all current and future threads of
// the given process. Note that this may not apply to threads that are created
// in parallel to the execution of this function.
BASE_EXPORT bool SetProcessCpuAffinityMode(ProcessHandle process_handle,
CpuAffinityMode affinity);
} // namespace base
#endif // BASE_CPU_AFFINITY_POSIX_H_
// 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 "base/cpu_affinity_posix.h"
#include <sched.h>
#include "base/synchronization/waitable_event.h"
#include "base/system/sys_info.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace {
class TestThread : public PlatformThread::Delegate {
public:
TestThread()
: termination_ready_(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED),
terminate_thread_(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED) {}
~TestThread() override {
EXPECT_TRUE(terminate_thread_.IsSignaled())
<< "Need to mark thread for termination and join the underlying thread "
<< "before destroying a FunctionTestThread as it owns the "
<< "WaitableEvent blocking the underlying thread's main.";
}
// Grabs |thread_id_|, signals |termination_ready_|, and then waits for
// |terminate_thread_| to be signaled before exiting.
void ThreadMain() override {
thread_id_ = PlatformThread::CurrentId();
EXPECT_NE(thread_id_, kInvalidThreadId);
// Make sure that the thread ID is the same across calls.
EXPECT_EQ(thread_id_, PlatformThread::CurrentId());
termination_ready_.Signal();
terminate_thread_.Wait();
done_ = true;
}
PlatformThreadId thread_id() const {
EXPECT_TRUE(termination_ready_.IsSignaled()) << "Thread ID still unknown";
return thread_id_;
}
bool IsRunning() const { return termination_ready_.IsSignaled() && !done_; }
// Blocks until this thread is started and ready to be terminated.
void WaitForTerminationReady() { termination_ready_.Wait(); }
// Marks this thread for termination (callers must then join this thread to be
// guaranteed of termination).
void MarkForTermination() { terminate_thread_.Signal(); }
private:
PlatformThreadId thread_id_ = kInvalidThreadId;
mutable WaitableEvent termination_ready_;
WaitableEvent terminate_thread_;
bool done_ = false;
DISALLOW_COPY_AND_ASSIGN(TestThread);
};
} // namespace
#if defined(OS_ANDROID)
#define MAYBE_SetThreadCpuAffinityMode SetThreadCpuAffinityMode
#else
// The test only considers Android device hardware models at the moment. Some
// CrOS devices on the waterfall have asymmetric CPUs that aren't covered. The
// linux-trusty-rel bot also fails to sched_setaffinity().
#define MAYBE_SetThreadCpuAffinityMode DISABLED_SetThreadCpuAffinityMode
#endif
TEST(CpuAffinityTest, MAYBE_SetThreadCpuAffinityMode) {
// This test currently only supports Nexus 5x and Pixel devices as big.LITTLE
// devices. For other devices, we assume that the cores are symmetric. This is
// currently the case for the devices on our waterfalls.
std::string device_model = SysInfo::HardwareModelName();
int expected_total_cores = SysInfo::SysInfo::NumberOfProcessors();
int expected_little_cores = expected_total_cores;
if (device_model == "Nexus 5X" || device_model == "Pixel 2" ||
device_model == "Pixel 2 XL" || device_model == "Pixel 3" ||
device_model == "Pixel 3 XL" || device_model == "Pixel 4" ||
device_model == "Pixel 4 XL") {
expected_little_cores = 4;
EXPECT_LT(expected_little_cores, expected_total_cores);
} else if (device_model == "Pixel" || device_model == "Pixel XL") {
expected_little_cores = 2;
EXPECT_LT(expected_little_cores, expected_total_cores);
} else if (device_model == "Pixel 3a" || device_model == "Pixel 3a XL") {
expected_little_cores = 6;
EXPECT_LT(expected_little_cores, expected_total_cores);
}
TestThread thread;
PlatformThreadHandle handle;
ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle));
thread.WaitForTerminationReady();
ASSERT_TRUE(thread.IsRunning());
PlatformThreadId thread_id = thread.thread_id();
cpu_set_t set;
EXPECT_TRUE(
SetThreadCpuAffinityMode(thread_id, CpuAffinityMode::kLittleCoresOnly));
EXPECT_EQ(sched_getaffinity(thread_id, sizeof(set), &set), 0);
EXPECT_EQ(CPU_COUNT(&set), expected_little_cores);
EXPECT_TRUE(SetThreadCpuAffinityMode(thread_id, CpuAffinityMode::kDefault));
EXPECT_EQ(sched_getaffinity(thread_id, sizeof(set), &set), 0);
EXPECT_EQ(CPU_COUNT(&set), expected_total_cores);
thread.MarkForTermination();
PlatformThread::Join(handle);
ASSERT_FALSE(thread.IsRunning());
}
} // namespace base
...@@ -12,7 +12,11 @@ ...@@ -12,7 +12,11 @@
#include <stdint.h> #include <stdint.h>
#include <unistd.h> #include <unistd.h>
#include "base/files/dir_reader_posix.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/process/process_handle.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/platform_thread.h"
namespace base { namespace base {
...@@ -99,6 +103,32 @@ TimeDelta GetUserCpuTimeSinceBoot(); ...@@ -99,6 +103,32 @@ TimeDelta GetUserCpuTimeSinceBoot();
// Converts Linux clock ticks to a wall time delta. // Converts Linux clock ticks to a wall time delta.
TimeDelta ClockTicksToTimeDelta(int clock_ticks); TimeDelta ClockTicksToTimeDelta(int clock_ticks);
// Executes the lambda for every task in the process's /proc/<pid>/task
// directory. The thread id and file path of the task directory are provided as
// arguments to the lambda.
template <typename Lambda>
void ForEachProcessTask(base::ProcessHandle process, Lambda&& lambda) {
// Iterate through the different threads tracked in /proc/<pid>/task.
FilePath fd_path = GetProcPidDir(process).Append("task");
DirReaderPosix dir_reader(fd_path.value().c_str());
if (!dir_reader.IsValid())
return;
for (; dir_reader.Next();) {
const char* tid_str = dir_reader.name();
if (strcmp(tid_str, ".") == 0 || strcmp(tid_str, "..") == 0)
continue;
PlatformThreadId tid;
if (!StringToInt(tid_str, &tid))
continue;
FilePath task_path = fd_path.Append(tid_str);
lambda(tid, task_path);
}
}
} // namespace internal } // namespace internal
} // namespace base } // namespace base
......
...@@ -188,32 +188,6 @@ void ReadChromeOSGraphicsMemory(SystemMemoryInfoKB* meminfo) { ...@@ -188,32 +188,6 @@ void ReadChromeOSGraphicsMemory(SystemMemoryInfoKB* meminfo) {
} }
#endif // defined(OS_CHROMEOS) #endif // defined(OS_CHROMEOS)
// Executes the lambda for every task in the process's /proc/<pid>/task
// directory. The thread id and file path of the task directory are provided as
// arguments to the lambda.
template <typename Lambda>
void ForEachProcessTask(base::ProcessHandle process, Lambda&& lambda) {
// Iterate through the different threads tracked in /proc/<pid>/task.
FilePath fd_path = internal::GetProcPidDir(process).Append("task");
DirReaderPosix dir_reader(fd_path.value().c_str());
if (!dir_reader.IsValid())
return;
for (; dir_reader.Next();) {
const char* tid_str = dir_reader.name();
if (strcmp(tid_str, ".") == 0 || strcmp(tid_str, "..") == 0)
continue;
PlatformThreadId tid;
if (!StringToInt(tid_str, &tid))
continue;
FilePath task_path = fd_path.Append(tid_str);
lambda(tid, task_path);
}
}
bool SupportsPerTaskTimeInState() { bool SupportsPerTaskTimeInState() {
FilePath time_in_state_path = internal::GetProcPidDir(GetCurrentProcId()) FilePath time_in_state_path = internal::GetProcPidDir(GetCurrentProcId())
.Append("task") .Append("task")
...@@ -245,21 +219,22 @@ bool ProcessMetrics::GetCumulativeCPUUsagePerThread( ...@@ -245,21 +219,22 @@ bool ProcessMetrics::GetCumulativeCPUUsagePerThread(
CPUUsagePerThread& cpu_per_thread) { CPUUsagePerThread& cpu_per_thread) {
cpu_per_thread.clear(); cpu_per_thread.clear();
ForEachProcessTask(process_, [&cpu_per_thread](PlatformThreadId tid, internal::ForEachProcessTask(
const FilePath& task_path) { process_,
FilePath thread_stat_path = task_path.Append("stat"); [&cpu_per_thread](PlatformThreadId tid, const FilePath& task_path) {
FilePath thread_stat_path = task_path.Append("stat");
std::string buffer; std::string buffer;
std::vector<std::string> proc_stats; std::vector<std::string> proc_stats;
if (!internal::ReadProcFile(thread_stat_path, &buffer) || if (!internal::ReadProcFile(thread_stat_path, &buffer) ||
!internal::ParseProcStats(buffer, &proc_stats)) { !internal::ParseProcStats(buffer, &proc_stats)) {
return; return;
} }
TimeDelta thread_time = TimeDelta thread_time = internal::ClockTicksToTimeDelta(
internal::ClockTicksToTimeDelta(ParseTotalCPUTimeFromStats(proc_stats)); ParseTotalCPUTimeFromStats(proc_stats));
cpu_per_thread.emplace_back(tid, thread_time); cpu_per_thread.emplace_back(tid, thread_time);
}); });
return !cpu_per_thread.empty(); return !cpu_per_thread.empty();
} }
...@@ -277,7 +252,7 @@ bool ProcessMetrics::GetPerThreadCumulativeCPUTimeInState( ...@@ -277,7 +252,7 @@ bool ProcessMetrics::GetPerThreadCumulativeCPUTimeInState(
return false; return false;
bool success = false; bool success = false;
ForEachProcessTask( internal::ForEachProcessTask(
process_, [&time_in_state_per_thread, &success, this]( process_, [&time_in_state_per_thread, &success, this](
PlatformThreadId tid, const FilePath& task_path) { PlatformThreadId tid, const FilePath& task_path) {
FilePath time_in_state_path = task_path.Append("time_in_state"); FilePath time_in_state_path = task_path.Append("time_in_state");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment