blob: 56962b1624bdbb0633bfc0d46ff8b2e2cf39d2ec [file] [log] [blame]
/*
* 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 "event_attr.h"
#include "event_fd.h"
#include "event_type.h"
#include "utils.h"
#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.
usleep(500000);
CHECK(!IsMpdecisionRunning());
}
void EnableMpdecision() {
int ret = __system_property_set("ctl.start", "mpdecision");
CHECK_EQ(0, ret);
usleep(500000);
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;
}
usleep(1000);
}
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;
};
static void CpuToggleThread(CpuToggleThreadArg* arg) {
while (!arg->end_flag) {
CHECK(SetCpuOnline(arg->toggle_cpu, true));
CHECK(SetCpuOnline(arg->toggle_cpu, false));
}
}
// 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;
cpu_toggle_arg.toggle_cpu = test_cpu;
cpu_toggle_arg.end_flag = false;
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;
const std::chrono::minutes test_duration(2); // Test for 2 minutes.
auto end_time = std::chrono::steady_clock::now() + test_duration;
size_t iterations = 0;
while (std::chrono::steady_clock::now() < end_time) {
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++;
GTEST_LOG_(INFO) << "Test offline while recording for " << iterations << " times.";
}
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;
cpu_toggle_arg.toggle_cpu = test_cpu;
cpu_toggle_arg.end_flag = false;
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;
const std::chrono::minutes test_duration(2); // Test for 2 minutes.
auto end_time = std::chrono::steady_clock::now() + test_duration;
size_t iterations = 0;
while (std::chrono::steady_clock::now() < end_time) {
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.
usleep(1000);
ASSERT_TRUE(event_fd->EnableEvent());
iterations++;
GTEST_LOG_(INFO) << "Test offline while ioctl(PERF_EVENT_IOC_ENABLE) for " << iterations << " times.";
}
cpu_toggle_arg.end_flag = true;
cpu_toggle_thread.join();
}
// 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;
ASSERT_TRUE(SetCpuOnline(test_cpu, true));
std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(attr, getpid(), record_cpu, nullptr);
ASSERT_TRUE(event_fd != nullptr);
ASSERT_TRUE(SetCpuOnline(test_cpu, false));
event_fd = nullptr;
event_fd = EventFd::OpenEventFile(attr, getpid(), record_cpu, nullptr);
ASSERT_TRUE(event_fd != nullptr);
}
}
int main(int argc, char** argv) {
InitLogging(argv, android::base::StderrLogger);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}