#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 <base/file.h>
+#include <base/logging.h>
+#include <base/stringprintf.h>
+#include "command.h"
#include "event_attr.h"
#include "event_fd.h"
#include "event_type.h"
-static std::unique_ptr<EventFd> OpenHardwareEventOnCpu0() {
- 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(), 0);
+static std::unique_ptr<Command> RecordCmd() {
+ return CreateCommandInstance("record");
}
-static const char* cpu1_online_path = "/sys/devices/system/cpu/cpu1/online";
+#if defined(__BIONIC__)
+class ScopedMpdecisionKiller {
+ public:
+ ScopedMpdecisionKiller() {
+ have_mpdecision_ = IsMpdecisionRunning();
+ if (have_mpdecision_) {
+ DisableMpdecision();
+ }
+ }
+
+ ~ScopedMpdecisionKiller() {
+ if (have_mpdecision_) {
+ EnableMpdecision();
+ }
+ }
-static bool HaveCpuOne() {
- struct stat st;
- return (stat(cpu1_online_path, &st) == 0 && S_ISREG(st.st_mode));
-}
+ 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;
+ }
-static void IsCpuOneOnline(bool* online, bool* has_error) {
+ 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;
- *has_error = true;
- ASSERT_TRUE(android::base::ReadFileToString(cpu1_online_path, &content));
- ASSERT_GT(content.size(), 0U);
- *has_error = false;
- *online = (content[0] == '0') ? false : true;
+ CHECK(android::base::ReadFileToString(filename, &content)) << "failed to read file " << filename;
+ return (content.find('1') != std::string::npos);
}
-static void SetCpuOneOnline(bool online, bool* has_error, bool* interrupted) {
- *interrupted = false;
- errno = 0;
- int ret = android::base::WriteStringToFile(online ? "1" : "0", cpu1_online_path);
- int saved_errno = errno;
- bool new_state;
- IsCpuOneOnline(&new_state, has_error);
- if (*has_error) {
- return;
- }
- if (new_state == online) {
+static void SetCpuOnline(int cpu, bool online) {
+ if (IsCpuOnline(cpu) == online) {
return;
- } else if (ret) {
- *interrupted = true;
- } else {
- *has_error = true;
- FAIL() << "Failed to SetCpuOneOnline, online = " << online
- << ", error = " << strerror(saved_errno) << ", new_state = " << new_state;
}
+ 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";
}
-// On some devices like flo, the kernel can't work correctly if a cpu
-// is offlined when perf is monitoring a hardware event.
-TEST(cpu_offline, smoke) {
- if (!HaveCpuOne()) {
- GTEST_LOG_(INFO) << "This test does nothing on uniprocessor devices.";
- return;
+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);
+ }
}
- bool has_error;
- bool interrupted;
- bool saved_online;
- bool success = false;
- IsCpuOneOnline(&saved_online, &has_error);
- // A loop is used in case the test is interrupted by other processes controling cpu hotplug, like
- // mpdecision.
- for (size_t loop_count = 0; !has_error && loop_count < 50; ++loop_count) {
- SetCpuOneOnline(true, &has_error, &interrupted);
- if (has_error || interrupted) {
- continue;
+ ~CpuOnlineRestorer() {
+ for (const auto& pair : online_map_) {
+ SetCpuOnline(pair.first, pair.second);
}
+ }
- std::unique_ptr<EventFd> event_fd = OpenHardwareEventOnCpu0();
- ASSERT_TRUE(event_fd != nullptr);
+ private:
+ std::unordered_map<int, bool> online_map_;
+};
- bool online;
- IsCpuOneOnline(&online, &has_error);
- if (has_error || !online) {
- continue;
+struct CpuToggleThreadArg {
+ int toggle_cpu;
+ std::atomic<bool> end_flag;
+};
+
+static void CpuToggleThread(CpuToggleThreadArg* arg) {
+ // Wait until a record command is running.
+ sleep(1);
+ while (!arg->end_flag) {
+ SetCpuOnline(arg->toggle_cpu, false);
+ sleep(1);
+ if (arg->end_flag) {
+ break;
}
- SetCpuOneOnline(false, &has_error, &interrupted);
- if (has_error || interrupted) {
- continue;
+ SetCpuOnline(arg->toggle_cpu, true);
+ 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});
+ exit(ret ? 0 : 1);
+ }
+ 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;
+ }
+
+ const size_t TEST_ITERATION_COUNT = 20u;
+ const int TEST_DURATION_IN_SECOND = 9;
+ for (size_t i = 0; i < TEST_ITERATION_COUNT; ++i) {
+ int test_cpu = GetCpuCount() - 1;
+ SetCpuOnline(test_cpu, true);
+ 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);
+
+ ASSERT_TRUE(RecordInChildProcess(test_cpu, TEST_DURATION_IN_SECOND));
+ cpu_toggle_arg.end_flag = true;
+ cpu_toggle_thread.join();
+ GTEST_LOG_(INFO) << "Finish test iteration " << (i + 1) << " successfully.";
+ }
+}
+
+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 = OpenHardwareEventOnCpu0();
+ event_fd = OpenHardwareEventOnCpu(record_cpu);
ASSERT_TRUE(event_fd != nullptr);
- success = true;
- break;
}
- SetCpuOneOnline(saved_online, &has_error, &interrupted);
- ASSERT_TRUE(success);
}