260 lines
7.5 KiB
C++
260 lines
7.5 KiB
C++
/*
|
|
* 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 <condition_variable>
|
|
#include <thread>
|
|
#include <unordered_map>
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/logging.h>
|
|
#include <android-base/stringprintf.h>
|
|
|
|
#include "command.h"
|
|
#include "event_attr.h"
|
|
#include "event_fd.h"
|
|
#include "event_type.h"
|
|
|
|
static std::unique_ptr<Command> RecordCmd() {
|
|
return CreateCommandInstance("record");
|
|
}
|
|
|
|
#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) {
|
|
std::string filename = android::base::StringPrintf("/sys/devices/system/cpu/cpu%d/online", cpu);
|
|
std::string content;
|
|
CHECK(android::base::ReadFileToString(filename, &content)) << "failed to read file " << filename;
|
|
return (content.find('1') != std::string::npos);
|
|
}
|
|
|
|
static void SetCpuOnline(int cpu, bool online) {
|
|
if (IsCpuOnline(cpu) == online) {
|
|
return;
|
|
}
|
|
std::string filename = android::base::StringPrintf("/sys/devices/system/cpu/cpu%d/online", cpu);
|
|
std::string content = online ? "1" : "0";
|
|
CHECK(android::base::WriteStringToFile(content, filename)) << "Write " << content << " to "
|
|
<< filename << " failed";
|
|
CHECK_EQ(online, IsCpuOnline(cpu)) << "set cpu " << cpu << (online ? " online" : " offline")
|
|
<< " failed";
|
|
}
|
|
|
|
static int GetCpuCount() {
|
|
return static_cast<int>(sysconf(_SC_NPROCESSORS_CONF));
|
|
}
|
|
|
|
class CpuOnlineRestorer {
|
|
public:
|
|
CpuOnlineRestorer() {
|
|
for (int cpu = 1; cpu < GetCpuCount(); ++cpu) {
|
|
online_map_[cpu] = IsCpuOnline(cpu);
|
|
}
|
|
}
|
|
|
|
~CpuOnlineRestorer() {
|
|
for (const auto& pair : online_map_) {
|
|
SetCpuOnline(pair.first, pair.second);
|
|
}
|
|
}
|
|
|
|
private:
|
|
std::unordered_map<int, bool> online_map_;
|
|
};
|
|
|
|
struct CpuToggleThreadArg {
|
|
int toggle_cpu;
|
|
std::atomic<bool> end_flag;
|
|
};
|
|
|
|
static void CpuToggleThread(CpuToggleThreadArg* arg) {
|
|
while (!arg->end_flag) {
|
|
SetCpuOnline(arg->toggle_cpu, true);
|
|
sleep(1);
|
|
SetCpuOnline(arg->toggle_cpu, false);
|
|
sleep(1);
|
|
}
|
|
}
|
|
|
|
static bool RecordInChildProcess(int record_cpu, int record_duration_in_second) {
|
|
pid_t pid = fork();
|
|
CHECK(pid != -1);
|
|
if (pid == 0) {
|
|
std::string cpu_str = android::base::StringPrintf("%d", record_cpu);
|
|
std::string record_duration_str = android::base::StringPrintf("%d", record_duration_in_second);
|
|
bool ret = RecordCmd()->Run({"-a", "--cpu", cpu_str, "sleep", record_duration_str});
|
|
extern bool system_wide_perf_event_open_failed;
|
|
// It is not an error if perf_event_open failed because of cpu-hotplug.
|
|
if (!ret && !system_wide_perf_event_open_failed) {
|
|
exit(1);
|
|
}
|
|
exit(0);
|
|
}
|
|
int timeout = record_duration_in_second + 10;
|
|
auto end_time = std::chrono::steady_clock::now() + std::chrono::seconds(timeout);
|
|
bool child_success = false;
|
|
while (std::chrono::steady_clock::now() < end_time) {
|
|
int exit_state;
|
|
pid_t ret = waitpid(pid, &exit_state, WNOHANG);
|
|
if (ret == pid) {
|
|
if (WIFSIGNALED(exit_state) || (WIFEXITED(exit_state) && WEXITSTATUS(exit_state) != 0)) {
|
|
child_success = false;
|
|
} else {
|
|
child_success = true;
|
|
}
|
|
break;
|
|
} else if (ret == -1) {
|
|
child_success = false;
|
|
break;
|
|
}
|
|
sleep(1);
|
|
}
|
|
return child_success;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
for (int i = 1; i < GetCpuCount(); ++i) {
|
|
if (!IsCpuOnline(i)) {
|
|
SetCpuOnline(i, true);
|
|
}
|
|
}
|
|
// Start cpu hotplugger.
|
|
int test_cpu = GetCpuCount() - 1;
|
|
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);
|
|
|
|
const std::chrono::hours test_duration(10); // Test for 10 hours.
|
|
const double RECORD_DURATION_IN_SEC = 2.9;
|
|
const double SLEEP_DURATION_IN_SEC = 1.3;
|
|
|
|
auto end_time = std::chrono::steady_clock::now() + test_duration;
|
|
size_t iterations = 0;
|
|
while (std::chrono::steady_clock::now() < end_time) {
|
|
iterations++;
|
|
GTEST_LOG_(INFO) << "Test for " << iterations << " times.";
|
|
ASSERT_TRUE(RecordInChildProcess(test_cpu, RECORD_DURATION_IN_SEC));
|
|
usleep(static_cast<useconds_t>(SLEEP_DURATION_IN_SEC * 1e6));
|
|
}
|
|
cpu_toggle_arg.end_flag = true;
|
|
cpu_toggle_thread.join();
|
|
}
|
|
|
|
static std::unique_ptr<EventFd> OpenHardwareEventOnCpu(int cpu) {
|
|
std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles");
|
|
if (event_type_modifier == nullptr) {
|
|
return nullptr;
|
|
}
|
|
perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
|
|
return EventFd::OpenEventFile(attr, getpid(), cpu);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
const size_t TEST_ITERATION_COUNT = 10u;
|
|
for (size_t i = 0; i < TEST_ITERATION_COUNT; ++i) {
|
|
int record_cpu = 0;
|
|
int toggle_cpu = GetCpuCount() - 1;
|
|
SetCpuOnline(toggle_cpu, true);
|
|
std::unique_ptr<EventFd> event_fd = OpenHardwareEventOnCpu(record_cpu);
|
|
ASSERT_TRUE(event_fd != nullptr);
|
|
SetCpuOnline(toggle_cpu, false);
|
|
event_fd = nullptr;
|
|
event_fd = OpenHardwareEventOnCpu(record_cpu);
|
|
ASSERT_TRUE(event_fd != nullptr);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
InitLogging(argv, android::base::StderrLogger);
|
|
testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|