OSDN Git Service

Simpleperf: add test for cpu offline whiling recording.
authorYabin Cui <yabinc@google.com>
Wed, 4 Nov 2015 02:00:01 +0000 (18:00 -0800)
committerYabin Cui <yabinc@google.com>
Wed, 4 Nov 2015 22:31:10 +0000 (14:31 -0800)
Bug: 25193162
Change-Id: I17b34f4e6932698e158719b884efe8ab441fbdef

simpleperf/cpu_offline_test.cpp

index 723518a..2a66c21 100644 (file)
 #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);
 }