You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
498 lines
15 KiB
498 lines
15 KiB
/* |
|
* Copyright (C) 2015 The Android Open Source Project |
|
* |
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
* you may not use this file except in compliance with the License. |
|
* You may obtain a copy of the License at |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, software |
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
* See the License for the specific language governing permissions and |
|
* limitations under the License. |
|
*/ |
|
|
|
#include <gtest/gtest.h> |
|
|
|
#include <sys/stat.h> |
|
#include <unistd.h> |
|
#if defined(__BIONIC__) |
|
#include <sys/system_properties.h> |
|
#endif |
|
|
|
#include <atomic> |
|
#include <chrono> |
|
#include <thread> |
|
#include <unordered_map> |
|
|
|
#include <android-base/file.h> |
|
#include <android-base/logging.h> |
|
#include <android-base/stringprintf.h> |
|
|
|
#include "environment.h" |
|
#include "event_attr.h" |
|
#include "event_fd.h" |
|
#include "event_type.h" |
|
#include "utils.h" |
|
|
|
static auto test_duration_for_long_tests = std::chrono::seconds(120); |
|
static auto cpu_hotplug_interval = std::chrono::microseconds(1000); |
|
static bool verbose_mode = false; |
|
|
|
#if defined(__BIONIC__) |
|
class ScopedMpdecisionKiller { |
|
public: |
|
ScopedMpdecisionKiller() { |
|
have_mpdecision_ = IsMpdecisionRunning(); |
|
if (have_mpdecision_) { |
|
DisableMpdecision(); |
|
} |
|
} |
|
|
|
~ScopedMpdecisionKiller() { |
|
if (have_mpdecision_) { |
|
EnableMpdecision(); |
|
} |
|
} |
|
|
|
private: |
|
bool IsMpdecisionRunning() { |
|
char value[PROP_VALUE_MAX]; |
|
int len = __system_property_get("init.svc.mpdecision", value); |
|
if (len == 0 || (len > 0 && strstr(value, "stopped") != nullptr)) { |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
void DisableMpdecision() { |
|
int ret = __system_property_set("ctl.stop", "mpdecision"); |
|
CHECK_EQ(0, ret); |
|
// Need to wait until mpdecision is actually stopped. |
|
std::this_thread::sleep_for(std::chrono::milliseconds(500)); |
|
CHECK(!IsMpdecisionRunning()); |
|
} |
|
|
|
void EnableMpdecision() { |
|
int ret = __system_property_set("ctl.start", "mpdecision"); |
|
CHECK_EQ(0, ret); |
|
std::this_thread::sleep_for(std::chrono::milliseconds(500)); |
|
CHECK(IsMpdecisionRunning()); |
|
} |
|
|
|
bool have_mpdecision_; |
|
}; |
|
#else |
|
class ScopedMpdecisionKiller { |
|
public: |
|
ScopedMpdecisionKiller() { |
|
} |
|
}; |
|
#endif |
|
|
|
static bool IsCpuOnline(int cpu, bool* has_error) { |
|
std::string filename = android::base::StringPrintf("/sys/devices/system/cpu/cpu%d/online", cpu); |
|
std::string content; |
|
bool ret = android::base::ReadFileToString(filename, &content); |
|
if (!ret) { |
|
PLOG(ERROR) << "failed to read file " << filename; |
|
*has_error = true; |
|
return false; |
|
} |
|
*has_error = false; |
|
return (content.find('1') != std::string::npos); |
|
} |
|
|
|
static bool SetCpuOnline(int cpu, bool online) { |
|
bool has_error; |
|
bool ret = IsCpuOnline(cpu, &has_error); |
|
if (has_error) { |
|
return false; |
|
} |
|
if (ret == online) { |
|
return true; |
|
} |
|
std::string filename = android::base::StringPrintf("/sys/devices/system/cpu/cpu%d/online", cpu); |
|
std::string content = online ? "1" : "0"; |
|
ret = android::base::WriteStringToFile(content, filename); |
|
if (!ret) { |
|
ret = IsCpuOnline(cpu, &has_error); |
|
if (has_error) { |
|
return false; |
|
} |
|
if (online == ret) { |
|
return true; |
|
} |
|
PLOG(ERROR) << "failed to write " << content << " to " << filename; |
|
return false; |
|
} |
|
// Kernel needs time to offline/online cpus, so use a loop to wait here. |
|
size_t retry_count = 0; |
|
while (true) { |
|
ret = IsCpuOnline(cpu, &has_error); |
|
if (has_error) { |
|
return false; |
|
} |
|
if (ret == online) { |
|
break; |
|
} |
|
LOG(ERROR) << "reading cpu retry count = " << retry_count << ", requested = " << online |
|
<< ", real = " << ret; |
|
if (++retry_count == 10000) { |
|
LOG(ERROR) << "setting cpu " << cpu << (online ? " online" : " offline") << " seems not to take effect"; |
|
return false; |
|
} |
|
std::this_thread::sleep_for(std::chrono::milliseconds(1)); |
|
} |
|
return true; |
|
} |
|
|
|
static int GetCpuCount() { |
|
return static_cast<int>(sysconf(_SC_NPROCESSORS_CONF)); |
|
} |
|
|
|
class CpuOnlineRestorer { |
|
public: |
|
CpuOnlineRestorer() { |
|
for (int cpu = 1; cpu < GetCpuCount(); ++cpu) { |
|
bool has_error; |
|
bool ret = IsCpuOnline(cpu, &has_error); |
|
if (has_error) { |
|
continue; |
|
} |
|
online_map_[cpu] = ret; |
|
} |
|
} |
|
|
|
~CpuOnlineRestorer() { |
|
for (const auto& pair : online_map_) { |
|
SetCpuOnline(pair.first, pair.second); |
|
} |
|
} |
|
|
|
private: |
|
std::unordered_map<int, bool> online_map_; |
|
}; |
|
|
|
bool FindAHotpluggableCpu(int* hotpluggable_cpu) { |
|
if (!IsRoot()) { |
|
GTEST_LOG_(INFO) << "This test needs root privilege to hotplug cpu."; |
|
return false; |
|
} |
|
for (int cpu = 1; cpu < GetCpuCount(); ++cpu) { |
|
bool has_error; |
|
bool online = IsCpuOnline(cpu, &has_error); |
|
if (has_error) { |
|
continue; |
|
} |
|
if (SetCpuOnline(cpu, !online)) { |
|
*hotpluggable_cpu = cpu; |
|
return true; |
|
} |
|
} |
|
GTEST_LOG_(INFO) << "There is no hotpluggable cpu."; |
|
return false; |
|
} |
|
|
|
struct CpuToggleThreadArg { |
|
int toggle_cpu; |
|
std::atomic<bool> end_flag; |
|
std::atomic<bool> cpu_hotplug_failed; |
|
|
|
CpuToggleThreadArg(int cpu) |
|
: toggle_cpu(cpu), end_flag(false), cpu_hotplug_failed(false) { |
|
} |
|
}; |
|
|
|
static void CpuToggleThread(CpuToggleThreadArg* arg) { |
|
while (!arg->end_flag) { |
|
if (!SetCpuOnline(arg->toggle_cpu, true)) { |
|
arg->cpu_hotplug_failed = true; |
|
break; |
|
} |
|
std::this_thread::sleep_for(cpu_hotplug_interval); |
|
if (!SetCpuOnline(arg->toggle_cpu, false)) { |
|
arg->cpu_hotplug_failed = true; |
|
break; |
|
} |
|
std::this_thread::sleep_for(cpu_hotplug_interval); |
|
} |
|
} |
|
|
|
// http://b/25193162. |
|
TEST(cpu_offline, offline_while_recording) { |
|
ScopedMpdecisionKiller scoped_mpdecision_killer; |
|
CpuOnlineRestorer cpuonline_restorer; |
|
if (GetCpuCount() == 1) { |
|
GTEST_LOG_(INFO) << "This test does nothing, because there is only one cpu in the system."; |
|
return; |
|
} |
|
// Start cpu hotpluger. |
|
int test_cpu; |
|
if (!FindAHotpluggableCpu(&test_cpu)) { |
|
return; |
|
} |
|
CpuToggleThreadArg cpu_toggle_arg(test_cpu); |
|
std::thread cpu_toggle_thread(CpuToggleThread, &cpu_toggle_arg); |
|
|
|
std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles"); |
|
ASSERT_TRUE(event_type_modifier != nullptr); |
|
perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type); |
|
attr.disabled = 0; |
|
attr.enable_on_exec = 0; |
|
|
|
auto start_time = std::chrono::steady_clock::now(); |
|
auto cur_time = start_time; |
|
auto end_time = std::chrono::steady_clock::now() + test_duration_for_long_tests; |
|
auto report_step = std::chrono::seconds(15); |
|
size_t iterations = 0; |
|
|
|
while (cur_time < end_time && !cpu_toggle_arg.cpu_hotplug_failed) { |
|
if (cur_time + report_step < std::chrono::steady_clock::now()) { |
|
// Report test time. |
|
auto diff = std::chrono::duration_cast<std::chrono::seconds>( |
|
std::chrono::steady_clock::now() - start_time); |
|
if (verbose_mode) { |
|
GTEST_LOG_(INFO) << "Have Tested " << (diff.count() / 60.0) << " minutes."; |
|
} |
|
cur_time = std::chrono::steady_clock::now(); |
|
} |
|
|
|
std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(attr, -1, test_cpu, nullptr, false); |
|
if (event_fd == nullptr) { |
|
// Failed to open because the test_cpu is offline. |
|
continue; |
|
} |
|
iterations++; |
|
if (verbose_mode) { |
|
GTEST_LOG_(INFO) << "Test offline while recording for " << iterations << " times."; |
|
} |
|
} |
|
if (cpu_toggle_arg.cpu_hotplug_failed) { |
|
GTEST_LOG_(INFO) << "Test ends because of cpu hotplug failure."; |
|
} |
|
cpu_toggle_arg.end_flag = true; |
|
cpu_toggle_thread.join(); |
|
} |
|
|
|
// http://b/25193162. |
|
TEST(cpu_offline, offline_while_ioctl_enable) { |
|
ScopedMpdecisionKiller scoped_mpdecision_killer; |
|
CpuOnlineRestorer cpuonline_restorer; |
|
if (GetCpuCount() == 1) { |
|
GTEST_LOG_(INFO) << "This test does nothing, because there is only one cpu in the system."; |
|
return; |
|
} |
|
// Start cpu hotpluger. |
|
int test_cpu; |
|
if (!FindAHotpluggableCpu(&test_cpu)) { |
|
return; |
|
} |
|
CpuToggleThreadArg cpu_toggle_arg(test_cpu); |
|
std::thread cpu_toggle_thread(CpuToggleThread, &cpu_toggle_arg); |
|
|
|
std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles"); |
|
ASSERT_TRUE(event_type_modifier != nullptr); |
|
perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type); |
|
attr.disabled = 1; |
|
attr.enable_on_exec = 0; |
|
|
|
auto start_time = std::chrono::steady_clock::now(); |
|
auto cur_time = start_time; |
|
auto end_time = std::chrono::steady_clock::now() + test_duration_for_long_tests; |
|
auto report_step = std::chrono::seconds(15); |
|
size_t iterations = 0; |
|
|
|
while (cur_time < end_time && !cpu_toggle_arg.cpu_hotplug_failed) { |
|
if (cur_time + report_step < std::chrono::steady_clock::now()) { |
|
// Report test time. |
|
auto diff = std::chrono::duration_cast<std::chrono::seconds>( |
|
std::chrono::steady_clock::now() - start_time); |
|
if (verbose_mode) { |
|
GTEST_LOG_(INFO) << "Have Tested " << (diff.count() / 60.0) << " minutes."; |
|
} |
|
cur_time = std::chrono::steady_clock::now(); |
|
|
|
} |
|
std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(attr, -1, test_cpu, nullptr, false); |
|
if (event_fd == nullptr) { |
|
// Failed to open because the test_cpu is offline. |
|
continue; |
|
} |
|
// Wait a little for the event to be installed on test_cpu's perf context. |
|
std::this_thread::sleep_for(std::chrono::milliseconds(1)); |
|
ASSERT_TRUE(event_fd->EnableEvent()); |
|
iterations++; |
|
if (verbose_mode) { |
|
GTEST_LOG_(INFO) << "Test offline while ioctl(PERF_EVENT_IOC_ENABLE) for " << iterations << " times."; |
|
} |
|
} |
|
if (cpu_toggle_arg.cpu_hotplug_failed) { |
|
GTEST_LOG_(INFO) << "Test ends because of cpu hotplug failure."; |
|
} |
|
cpu_toggle_arg.end_flag = true; |
|
cpu_toggle_thread.join(); |
|
} |
|
|
|
struct CpuSpinThreadArg { |
|
int spin_cpu; |
|
std::atomic<pid_t> tid; |
|
std::atomic<bool> end_flag; |
|
}; |
|
|
|
static void CpuSpinThread(CpuSpinThreadArg* arg) { |
|
arg->tid = gettid(); |
|
while (!arg->end_flag) { |
|
cpu_set_t mask; |
|
CPU_ZERO(&mask); |
|
CPU_SET(arg->spin_cpu, &mask); |
|
// If toggle_cpu is offline, setaffinity fails. So call it in a loop to |
|
// make sure current thread mostly runs on toggle_cpu. |
|
sched_setaffinity(arg->tid, sizeof(mask), &mask); |
|
} |
|
} |
|
|
|
// http://b/28086229. |
|
TEST(cpu_offline, offline_while_user_process_profiling) { |
|
ScopedMpdecisionKiller scoped_mpdecision_killer; |
|
CpuOnlineRestorer cpuonline_restorer; |
|
// Start cpu hotpluger. |
|
int test_cpu; |
|
if (!FindAHotpluggableCpu(&test_cpu)) { |
|
return; |
|
} |
|
CpuToggleThreadArg cpu_toggle_arg(test_cpu); |
|
std::thread cpu_toggle_thread(CpuToggleThread, &cpu_toggle_arg); |
|
|
|
// Start cpu spinner. |
|
CpuSpinThreadArg cpu_spin_arg; |
|
cpu_spin_arg.spin_cpu = test_cpu; |
|
cpu_spin_arg.tid = 0; |
|
cpu_spin_arg.end_flag = false; |
|
std::thread cpu_spin_thread(CpuSpinThread, &cpu_spin_arg); |
|
while (cpu_spin_arg.tid == 0) { |
|
std::this_thread::sleep_for(std::chrono::milliseconds(1)); |
|
} |
|
|
|
std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles"); |
|
ASSERT_TRUE(event_type_modifier != nullptr); |
|
perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type); |
|
// Enable profiling in perf_event_open system call. |
|
attr.disabled = 0; |
|
attr.enable_on_exec = 0; |
|
|
|
auto start_time = std::chrono::steady_clock::now(); |
|
auto cur_time = start_time; |
|
auto end_time = start_time + test_duration_for_long_tests; |
|
auto report_step = std::chrono::seconds(15); |
|
size_t iterations = 0; |
|
|
|
while (cur_time < end_time && !cpu_toggle_arg.cpu_hotplug_failed) { |
|
if (cur_time + report_step < std::chrono::steady_clock::now()) { |
|
auto diff = std::chrono::duration_cast<std::chrono::seconds>( |
|
std::chrono::steady_clock::now() - start_time); |
|
if (verbose_mode) { |
|
GTEST_LOG_(INFO) << "Have Tested " << (diff.count() / 60.0) << " minutes."; |
|
} |
|
cur_time = std::chrono::steady_clock::now(); |
|
} |
|
// Test if the cpu pmu is still usable. |
|
ASSERT_TRUE(EventFd::OpenEventFile(attr, 0, -1, nullptr, true) != nullptr); |
|
|
|
std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(attr, cpu_spin_arg.tid, |
|
test_cpu, nullptr, false); |
|
if (event_fd == nullptr) { |
|
// Failed to open because the test_cpu is offline. |
|
continue; |
|
} |
|
// profile for a while. |
|
std::this_thread::sleep_for(std::chrono::milliseconds(1)); |
|
iterations++; |
|
if (verbose_mode) { |
|
GTEST_LOG_(INFO) << "Test offline while user process profiling for " << iterations << " times."; |
|
} |
|
} |
|
if (cpu_toggle_arg.cpu_hotplug_failed) { |
|
GTEST_LOG_(INFO) << "Test ends because of cpu hotplug failure."; |
|
} |
|
cpu_toggle_arg.end_flag = true; |
|
cpu_toggle_thread.join(); |
|
cpu_spin_arg.end_flag = true; |
|
cpu_spin_thread.join(); |
|
// Check if the cpu-cycle event is still available on test_cpu. |
|
if (SetCpuOnline(test_cpu, true)) { |
|
ASSERT_TRUE(EventFd::OpenEventFile(attr, -1, test_cpu, nullptr, true) != nullptr); |
|
} |
|
} |
|
|
|
// http://b/19863147. |
|
TEST(cpu_offline, offline_while_recording_on_another_cpu) { |
|
ScopedMpdecisionKiller scoped_mpdecision_killer; |
|
CpuOnlineRestorer cpuonline_restorer; |
|
|
|
if (GetCpuCount() == 1) { |
|
GTEST_LOG_(INFO) << "This test does nothing, because there is only one cpu in the system."; |
|
return; |
|
} |
|
int test_cpu; |
|
if (!FindAHotpluggableCpu(&test_cpu)) { |
|
return; |
|
} |
|
std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles"); |
|
perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type); |
|
attr.disabled = 0; |
|
attr.enable_on_exec = 0; |
|
|
|
const size_t TEST_ITERATION_COUNT = 10u; |
|
for (size_t i = 0; i < TEST_ITERATION_COUNT; ++i) { |
|
int record_cpu = 0; |
|
if (!SetCpuOnline(test_cpu, true)) { |
|
break; |
|
} |
|
std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(attr, getpid(), record_cpu, nullptr); |
|
ASSERT_TRUE(event_fd != nullptr); |
|
if (!SetCpuOnline(test_cpu, false)) { |
|
break; |
|
} |
|
event_fd = nullptr; |
|
event_fd = EventFd::OpenEventFile(attr, getpid(), record_cpu, nullptr); |
|
ASSERT_TRUE(event_fd != nullptr); |
|
} |
|
} |
|
|
|
int main(int argc, char** argv) { |
|
for (int i = 1; i < argc; ++i) { |
|
if (strcmp(argv[i], "--help") == 0) { |
|
printf("--long_test_duration <second> Set test duration for long tests. Default is 120s.\n"); |
|
printf("--cpu_hotplug_interval <microseconds> Set cpu hotplug interval. Default is 1000us.\n"); |
|
printf("--verbose Show verbose log.\n"); |
|
} else if (strcmp(argv[i], "--long_test_duration") == 0) { |
|
if (i + 1 < argc) { |
|
int second_count = atoi(argv[i+1]); |
|
if (second_count <= 0) { |
|
fprintf(stderr, "Invalid arg for --long_test_duration.\n"); |
|
return 1; |
|
} |
|
test_duration_for_long_tests = std::chrono::seconds(second_count); |
|
i++; |
|
} |
|
} else if (strcmp(argv[i], "--cpu_hotplug_interval") == 0) { |
|
if (i + 1 < argc) { |
|
int microsecond_count = atoi(argv[i+1]); |
|
if (microsecond_count <= 0) { |
|
fprintf(stderr, "Invalid arg for --cpu_hotplug_interval\n"); |
|
return 1; |
|
} |
|
cpu_hotplug_interval = std::chrono::microseconds(microsecond_count); |
|
i++; |
|
} |
|
} else if (strcmp(argv[i], "--verbose") == 0) { |
|
verbose_mode = true; |
|
} |
|
} |
|
android::base::InitLogging(argv, android::base::StderrLogger); |
|
testing::InitGoogleTest(&argc, argv); |
|
return RUN_ALL_TESTS(); |
|
}
|
|
|