You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by ji...@apache.org on 2015/07/25 01:38:01 UTC

[08/12] mesos git commit: Moved containerizer related tests under src/tests/containerizer.

http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/memory_test_helper.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/memory_test_helper.cpp b/src/tests/containerizer/memory_test_helper.cpp
new file mode 100644
index 0000000..48a3563
--- /dev/null
+++ b/src/tests/containerizer/memory_test_helper.cpp
@@ -0,0 +1,321 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.n
+ */
+
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <string>
+#include <vector>
+
+#include <stout/bytes.hpp>
+#include <stout/error.hpp>
+#include <stout/hashmap.hpp>
+#include <stout/lambda.hpp>
+#include <stout/os.hpp>
+#include <stout/stringify.hpp>
+#include <stout/strings.hpp>
+#include <stout/try.hpp>
+
+#include "tests/flags.hpp"
+
+#include "tests/containerizer/memory_test_helper.hpp"
+
+using process::Subprocess;
+
+using std::cerr;
+using std::cin;
+using std::cout;
+using std::endl;
+using std::flush;
+using std::getline;
+using std::string;
+using std::vector;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+// Constants used to sync MemoryTestHelper and its subprocess.
+
+// Used by the subprocess to inform that it has started.
+const char STARTED = 'S';
+
+// Used by the subprocess to inform that the work requested is done.
+const char DONE = 'D';
+
+// Used to signal an increaseRSS request.
+const char INCREASE_RSS[] = "INCREASE_RSS";
+
+// Used to signal an increasePageCache request.
+const char INCREASE_PAGE_CACHE[] = "INCREASE_PAGE_CACHE";
+
+
+// This helper allocates and locks specified anonymous memory (RSS).
+// It uses mlock and memset to make sure allocated memory is mapped.
+static Try<void*> allocateRSS(const Bytes& size, bool lock = true)
+{
+  void* rss = NULL;
+
+  if (posix_memalign(&rss, getpagesize(), size.bytes()) != 0) {
+    return ErrnoError("Failed to increase RSS memory, posix_memalign");
+  }
+
+  // Use memset to actually page in the memory in the kernel.
+  memset(rss, 1, size.bytes());
+
+  // Locking a page makes it unevictable in the kernel.
+  if (lock && mlock(rss, size.bytes()) != 0) {
+    return ErrnoError("Failed to lock memory, mlock");
+  }
+
+  return rss;
+}
+
+
+static Try<Nothing> increaseRSS(const vector<string>& tokens)
+{
+  if (tokens.size() < 2) {
+    return Error("Expect at least one argument");
+  }
+
+  Try<Bytes> size = Bytes::parse(tokens[1]);
+  if (size.isError()) {
+    return Error("The first argument '" + tokens[1] + "' is not a byte size");
+  }
+
+  Try<void*> memory = allocateRSS(size.get());
+  if (memory.isError()) {
+    return Error("Failed to allocate RSS memory: " + memory.error());
+  }
+
+  return Nothing();
+}
+
+
+static Try<Nothing> increasePageCache(const vector<string>& tokens)
+{
+  const Bytes UNIT = Megabytes(1);
+
+  if (tokens.size() < 2) {
+    return Error("Expect at least one argument");
+  }
+
+  Try<Bytes> size = Bytes::parse(tokens[1]);
+  if (size.isError()) {
+    return Error("The first argument '" + tokens[1] + "' is not a byte size");
+  }
+
+  // TODO(chzhcn): Currently, we assume the current working directory
+  // is a temporary directory and will be cleaned up when the test
+  // finishes. Since the child process will inherit the current
+  // working directory from the parent process, that means the test
+  // that uses this helper probably needs to inherit from
+  // TemporaryDirectoryTest. Consider relaxing this constraint.
+  Try<string> path = os::mktemp(path::join(os::getcwd(), "XXXXXX"));
+  if (path.isError()) {
+    return Error("Failed to create a temporary file: " + path.error());
+  }
+
+  Try<int> fd = os::open(path.get(), O_WRONLY);
+  if (fd.isError()) {
+    return Error("Failed to open file: " + fd.error());
+  }
+
+  // NOTE: We are doing round-down here to calculate the number of
+  // writes to do.
+  for (uint64_t i = 0; i < size.get().bytes() / UNIT.bytes(); i++) {
+    // Write UNIT size to disk at a time. The content isn't important.
+    Try<Nothing> write = os::write(fd.get(), string(UNIT.bytes(), 'a'));
+    if (write.isError()) {
+      os::close(fd.get());
+      return Error("Failed to write file: " + write.error());
+    }
+
+    // Use fsync to make sure data is written to disk.
+    if (fsync(fd.get()) == -1) {
+      // Save the error message because os::close below might
+      // overwrite the errno.
+      const string message = strerror(errno);
+
+      os::close(fd.get());
+      return Error("Failed to fsync: " + message);
+    }
+  }
+
+  os::close(fd.get());
+  return Nothing();
+}
+
+
+MemoryTestHelper::~MemoryTestHelper()
+{
+  cleanup();
+}
+
+
+Try<Nothing> MemoryTestHelper::spawn()
+{
+  if (s.isSome()) {
+    return Error("A subprocess has been spawned already");
+  }
+
+  vector<string> argv;
+  argv.push_back("memory-test-helper");
+  argv.push_back(MemoryTestHelperMain::NAME);
+
+  Try<Subprocess> process = subprocess(
+      path::join(flags.build_dir,
+                 "src",
+                 "memory-test-helper"),
+      argv,
+      Subprocess::PIPE(),
+      Subprocess::PIPE(),
+      Subprocess::FD(STDERR_FILENO));
+
+  if (process.isError()) {
+    return Error("Failed to spawn a subprocess: " + process.error());
+  }
+
+  s = process.get();
+
+  // Wait for the child to inform it has started before returning.
+  // Otherwise, the user might set the memory limit too earlier, and
+  // cause the child oom-killed because 'ld' could use a lot of
+  // memory.
+  Result<string> read = os::read(s.get().out().get(), sizeof(STARTED));
+  if (!read.isSome() || read.get() != string(sizeof(STARTED), STARTED)) {
+    cleanup();
+    return Error("Failed to sync with the subprocess");
+  }
+
+  return Nothing();
+}
+
+
+void MemoryTestHelper::cleanup()
+{
+  if (s.isSome()) {
+    // We just want to make sure the subprocess is terminated in case
+    // it's stuck, but we don't care about its status. Any error
+    // should have been logged in the subprocess directly.
+    ::kill(s.get().pid(), SIGKILL);
+    ::waitpid(s.get().pid(), NULL, 0);
+    s = None();
+  }
+}
+
+
+Try<pid_t> MemoryTestHelper::pid()
+{
+  if (s.isNone()) {
+    return Error("The subprocess has not been spawned yet");
+  }
+
+  return s.get().pid();
+}
+
+
+// Send a request to the subprocess and wait for its signal that the
+// work has been done.
+Try<Nothing> MemoryTestHelper::requestAndWait(const string& request)
+{
+  if (s.isNone()) {
+    return Error("The subprocess has not been spawned yet");
+  }
+
+  Try<Nothing> write = os::write(s.get().in().get(), request + "\n");
+  if (write.isError()) {
+    cleanup();
+    return Error("Fail to sync with the subprocess: " + write.error());
+  }
+
+  Result<string> read = os::read(s.get().out().get(), sizeof(DONE));
+  if (!read.isSome() || read.get() != string(sizeof(DONE), DONE)) {
+    cleanup();
+    return Error("Failed to sync with the subprocess");
+  }
+
+  return Nothing();
+}
+
+
+Try<Nothing> MemoryTestHelper::increaseRSS(const Bytes& size)
+{
+  return requestAndWait(string(INCREASE_RSS) + " " + stringify(size));
+}
+
+
+Try<Nothing> MemoryTestHelper::increasePageCache(const Bytes& size)
+{
+  return requestAndWait(string(INCREASE_PAGE_CACHE) + " " + stringify(size));
+}
+
+
+const char MemoryTestHelperMain::NAME[] = "MemoryTestHelperMain";
+
+
+int MemoryTestHelperMain::execute()
+{
+  hashmap<string, Try<Nothing>(*)(const vector<string>&)> commands;
+  commands[INCREASE_RSS] = &increaseRSS;
+  commands[INCREASE_PAGE_CACHE] = &increasePageCache;
+
+  // Tell the parent that child has started.
+  cout << STARTED << flush;
+
+  string line;
+  while(cin.good()) {
+    getline(cin, line);
+    vector<string> tokens = strings::tokenize(line, " ");
+
+    if (tokens.empty()) {
+      cerr << "No command from the parent" << endl;
+      return 1;
+    }
+
+    if (!commands.contains(tokens[0])) {
+      cerr << "Unknown command from the parent '" << tokens[0] << "'" << endl;
+      return 1;
+    }
+
+    Try<Nothing> result = commands[tokens[0]](tokens);
+    if (result.isError()) {
+      cerr << result.error();
+      return 1;
+    }
+
+    cout << DONE << flush;
+  }
+
+  if (!cin) {
+    cerr << "Failed to sync with the parent" << endl;
+    return 1;
+  }
+
+  return 0;
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/memory_test_helper.hpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/memory_test_helper.hpp b/src/tests/containerizer/memory_test_helper.hpp
new file mode 100644
index 0000000..11712d7
--- /dev/null
+++ b/src/tests/containerizer/memory_test_helper.hpp
@@ -0,0 +1,89 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+#ifndef __MEMORY_TEST_HELPER_HPP__
+#define __MEMORY_TEST_HELPER_HPP__
+
+#include <process/subprocess.hpp>
+
+#include <stout/bytes.hpp>
+#include <stout/option.hpp>
+#include <stout/subcommand.hpp>
+#include <stout/try.hpp>
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+// The abstraction for controlling the memory usage of a subprocess.
+// TODO(chzhcn): Currently, this helper is only supposed to be used by
+// one thread. Consider making it thread safe.
+class MemoryTestHelper
+{
+public:
+  MemoryTestHelper() {};
+  ~MemoryTestHelper();
+
+  // Spawns a subprocess.
+  // TODO(chzhcn): Consider returning a future instead of blocking.
+  Try<Nothing> spawn();
+
+  // Kill and reap the subprocess if exists.
+  // TODO(chzhcn): Consider returning a future instead of blocking.
+  void cleanup();
+
+  // Returns the pid of the subprocess.
+  Try<pid_t> pid();
+
+  // Allocate and lock specified page-aligned anonymous memory (RSS)
+  // in the subprocess. It uses mlock and memset to make sure
+  // allocated memory is mapped.
+  // TODO(chzhcn): Consider returning a future instead of blocking.
+  Try<Nothing> increaseRSS(const Bytes& size);
+
+  // This function attempts to generate requested size of page cache
+  // in the subprocess by using a small buffer and writing it to disk
+  // multiple times.
+  // TODO(chzhcn): Consider returning a future instead of blocking.
+  Try<Nothing> increasePageCache(const Bytes& size = Megabytes(1));
+
+private:
+  Try<Nothing> requestAndWait(const std::string& request);
+
+  Option<process::Subprocess> s;
+};
+
+
+// The actual subprocess behind MemoryTestHelper. It runs in a loop
+// and executes commands passed from stdin.
+class MemoryTestHelperMain : public Subcommand
+{
+public:
+  static const char NAME[];
+
+  MemoryTestHelperMain() : Subcommand(NAME) {};
+
+protected:
+  virtual int execute();
+};
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
+
+#endif // __MEMORY_TEST_HELPER_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/memory_test_helper_main.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/memory_test_helper_main.cpp b/src/tests/containerizer/memory_test_helper_main.cpp
new file mode 100644
index 0000000..df98cbb
--- /dev/null
+++ b/src/tests/containerizer/memory_test_helper_main.cpp
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 <stout/subcommand.hpp>
+
+#include "tests/containerizer/memory_test_helper.hpp"
+
+using mesos::internal::tests::MemoryTestHelperMain;
+
+int main(int argc, char** argv)
+{
+  return Subcommand::dispatch(
+      None(),
+      argc,
+      argv,
+      new MemoryTestHelperMain());
+}

http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/ns_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/ns_tests.cpp b/src/tests/containerizer/ns_tests.cpp
new file mode 100644
index 0000000..c71c33f
--- /dev/null
+++ b/src/tests/containerizer/ns_tests.cpp
@@ -0,0 +1,302 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 <sys/wait.h>
+
+#include <iostream>
+
+#include <pthread.h>
+#include <unistd.h>
+
+#include <list>
+#include <set>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include <stout/gtest.hpp>
+#include <stout/lambda.hpp>
+#include <stout/os.hpp>
+
+#include <process/gtest.hpp>
+#include <process/subprocess.hpp>
+
+#include "linux/ns.hpp"
+
+#include "tests/flags.hpp"
+
+#include "tests/containerizer/setns_test_helper.hpp"
+
+using namespace process;
+
+using std::list;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+
+// Helper for cloneChild() which expects an int(void*).
+static int cloneChildHelper(void* _func)
+{
+  const lambda::function<int()>* func =
+    static_cast<const lambda::function<int()>*> (_func);
+
+  return (*func)();
+}
+
+
+static pid_t cloneChild(
+    int flags,
+    const lambda::function<int()>& func)
+
+{
+  // 8 MiB stack for child.
+  static unsigned long long stack[(8*1024*1024)/sizeof(unsigned long long)];
+
+  return ::clone(
+      cloneChildHelper,
+      &stack[sizeof(stack)/sizeof(stack[0]) - 1], // Stack grows down.
+      flags | SIGCHLD,
+      (void*) &func);
+}
+
+
+// Test that a child in different namespace(s) can setns back to the
+// root namespace. We must fork a child to test this because setns
+// doesn't support multi-threaded processes (which gtest is).
+TEST(NsTest, ROOT_setns)
+{
+  // Clone then exec the setns-test-helper into a new namespace for
+  // each available namespace.
+  set<string> namespaces = ns::namespaces();
+  ASSERT_FALSE(namespaces.empty());
+
+  int flags = 0;
+
+  foreach (const string& ns, namespaces) {
+    // Skip 'user' namespace because it causes 'clone' to change us
+    // from being user 'root' to user 'nobody', but these tests
+    // require root. See MESOS-3083.
+    if (ns == "user") {
+      continue;
+    }
+
+    Try<int> nstype = ns::nstype(ns);
+    ASSERT_SOME(nstype);
+
+    flags |= nstype.get();
+  }
+
+  vector<string> argv;
+  argv.push_back("setns-test-helper");
+  argv.push_back(SetnsTestHelper::NAME);
+
+  Try<Subprocess> s = subprocess(
+      path::join(tests::flags.build_dir, "src", "setns-test-helper"),
+      argv,
+      Subprocess::FD(STDIN_FILENO),
+      Subprocess::FD(STDOUT_FILENO),
+      Subprocess::FD(STDERR_FILENO),
+      None(),
+      None(),
+      None(),
+      lambda::bind(&cloneChild, flags, lambda::_1));
+
+  // Continue in parent.
+  ASSERT_SOME(s);
+
+  // The child should exit 0.
+  Future<Option<int>> status = s.get().status();
+  AWAIT_READY(status);
+
+  ASSERT_SOME(status.get());
+  EXPECT_TRUE(WIFEXITED(status.get().get()));
+  EXPECT_EQ(0, status.get().get());
+}
+
+
+static void* childThread(void* arg)
+{
+  // Newly created threads have PTHREAD_CANCEL_ENABLE and
+  // PTHREAD_CANCEL_DEFERRED so they can be cancelled.
+  while (true) { os::sleep(Seconds(1)); }
+
+  return NULL;
+}
+
+
+// Test that setns correctly refuses to re-associate to a namespace if
+// the caller is multi-threaded.
+TEST(NsTest, ROOT_setnsMultipleThreads)
+{
+  set<string> namespaces = ns::namespaces();
+  EXPECT_LT(0u, namespaces.size());
+
+  // Do not allow multi-threaded environment.
+  pthread_t pthread;
+  ASSERT_EQ(0, pthread_create(&pthread, NULL, childThread, NULL));
+
+  foreach (const string& ns, namespaces) {
+    EXPECT_ERROR(ns::setns(::getpid(), ns));
+  }
+
+  // Terminate the threads.
+  EXPECT_EQ(0, pthread_cancel(pthread));
+  EXPECT_EQ(0, pthread_join(pthread, NULL));
+}
+
+
+// Use a different child function for clone because it requires
+// int(*)(void*).
+static int childGetns(void* arg)
+{
+  // Sleep until killed.
+  while (true) { sleep(1); }
+
+  ABORT("Error, child should be killed before reaching here");
+}
+
+
+// Test that we can get the namespace inodes for a forked child.
+TEST(NsTest, ROOT_getns)
+{
+  set<string> namespaces = ns::namespaces();
+
+  // ns::setns() does not support "pid".
+  namespaces.erase("pid");
+
+  // Use the first other namespace available.
+  ASSERT_FALSE(namespaces.empty());
+  string ns = *(namespaces.begin());
+
+  ASSERT_SOME(ns::getns(::getpid(), ns));
+
+  Try<int> nstype = ns::nstype(ns);
+  ASSERT_SOME(nstype);
+
+  // 8 MiB stack for child.
+  static unsigned long long stack[(8*1024*1024)/sizeof(unsigned long long)];
+
+  pid_t pid = clone(
+      childGetns,
+      &stack[sizeof(stack)/sizeof(stack[0]) - 1], // Stack grows down.
+      SIGCHLD | nstype.get(),
+      NULL);
+
+  ASSERT_NE(-1, pid);
+
+  // Continue in parent.
+  Try<ino_t> nsParent = ns::getns(::getpid(), ns);
+  ASSERT_SOME(nsParent);
+
+  Try<ino_t> nsChild = ns::getns(pid, ns);
+  ASSERT_SOME(nsChild);
+
+  // Child should be in a different namespace.
+  EXPECT_NE(nsParent.get(), nsChild.get());
+
+  // Kill the child process.
+  ASSERT_NE(-1, ::kill(pid, SIGKILL));
+
+  // Wait for the child process.
+  int status;
+  EXPECT_NE(-1, ::waitpid((pid_t) -1, &status, 0));
+  ASSERT_TRUE(WIFSIGNALED(status));
+  EXPECT_EQ(SIGKILL, WTERMSIG(status));
+}
+
+
+static int childDestroy(void* arg)
+{
+  // Fork a bunch of children.
+  ::fork();
+  ::fork();
+  ::fork();
+
+  // Parent and all children sleep.
+  while (true) { sleep(1); }
+
+  ABORT("Error, child should be killed before reaching here");
+}
+
+
+// Test we can destroy a pid namespace, i.e., kill all processes.
+TEST(NsTest, ROOT_destroy)
+{
+  set<string> namespaces = ns::namespaces();
+
+  if (namespaces.count("pid") == 0) {
+    // Pid namespace is not available.
+    return;
+  }
+
+  Try<int> nstype = ns::nstype("pid");
+  ASSERT_SOME(nstype);
+
+  // 8 MiB stack for child.
+  static unsigned long long stack[(8*1024*1024)/sizeof(unsigned long long)];
+
+  pid_t pid = clone(
+      childDestroy,
+      &stack[sizeof(stack)/sizeof(stack[0]) - 1], // Stack grows down.
+      SIGCHLD | nstype.get(),
+      NULL);
+
+  ASSERT_NE(-1, pid);
+
+  Future<Option<int>> status = process::reap(pid);
+
+  // Ensure the child is in a different pid namespace.
+  Try<ino_t> childNs = ns::getns(pid, "pid");
+  ASSERT_SOME(childNs);
+
+  Try<ino_t> ourNs = ns::getns(::getpid(), "pid");
+  ASSERT_SOME(ourNs);
+
+  ASSERT_NE(ourNs.get(), childNs.get());
+
+  // Kill the child.
+  AWAIT_READY(ns::pid::destroy(childNs.get()));
+
+  AWAIT_READY(status);
+  ASSERT_SOME(status.get());
+  ASSERT_TRUE(WIFSIGNALED(status.get().get()));
+  EXPECT_EQ(SIGKILL, WTERMSIG(status.get().get()));
+
+  // Finally, verify that no processes are in the child's pid
+  // namespace, i.e., destroy() also killed all descendants.
+  Try<set<pid_t>> pids = os::pids();
+  ASSERT_SOME(pids);
+
+  foreach (pid_t pid, pids.get()) {
+    Try<ino_t> otherNs = ns::getns(pid, "pid");
+    // pid may have exited since getting the snapshot of pids so
+    // ignore any error.
+    if (otherNs.isSome()) {
+      ASSERT_SOME_NE(childNs.get(), otherNs);
+    }
+  }
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/perf_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/perf_tests.cpp b/src/tests/containerizer/perf_tests.cpp
new file mode 100644
index 0000000..6b3d70f
--- /dev/null
+++ b/src/tests/containerizer/perf_tests.cpp
@@ -0,0 +1,183 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 <sys/prctl.h>
+
+#include <set>
+
+#include <gmock/gmock.h>
+
+#include <process/clock.hpp>
+#include <process/gtest.hpp>
+#include <process/reap.hpp>
+
+#include <stout/gtest.hpp>
+#include <stout/stringify.hpp>
+
+#include "linux/perf.hpp"
+
+using std::set;
+using std::string;
+
+using namespace process;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+class PerfTest : public ::testing::Test {};
+
+
+TEST_F(PerfTest, ROOT_Events)
+{
+  set<string> events;
+  // Valid events.
+  events.insert("cycles");
+  events.insert("task-clock");
+  EXPECT_TRUE(perf::valid(events));
+
+  // Add an invalid event.
+  events.insert("this-is-an-invalid-event");
+  EXPECT_FALSE(perf::valid(events));
+}
+
+
+TEST_F(PerfTest, Parse)
+{
+  // uint64 and floats should be parsed.
+  Try<hashmap<string, mesos::PerfStatistics> > parse =
+    perf::parse("123,cycles\n0.123,task-clock");
+  CHECK_SOME(parse);
+
+  ASSERT_TRUE(parse.get().contains(""));
+  mesos::PerfStatistics statistics = parse.get().get("").get();
+
+  ASSERT_TRUE(statistics.has_cycles());
+  EXPECT_EQ(123u, statistics.cycles());
+  ASSERT_TRUE(statistics.has_task_clock());
+  EXPECT_EQ(0.123, statistics.task_clock());
+
+  // Parse multiple cgroups.
+  parse = perf::parse("123,cycles,cgroup1\n"
+                      "456,cycles,cgroup2\n"
+                      "0.456,task-clock,cgroup2\n"
+                      "0.123,task-clock,cgroup1");
+  CHECK_SOME(parse);
+  EXPECT_FALSE(parse.get().contains(""));
+
+  ASSERT_TRUE(parse.get().contains("cgroup1"));
+  statistics = parse.get().get("cgroup1").get();
+
+  ASSERT_TRUE(statistics.has_cycles());
+  EXPECT_EQ(123u, statistics.cycles());
+  ASSERT_TRUE(statistics.has_task_clock());
+  EXPECT_EQ(0.123, statistics.task_clock());
+
+  ASSERT_TRUE(parse.get().contains("cgroup2"));
+  statistics = parse.get().get("cgroup2").get();
+
+  ASSERT_TRUE(statistics.has_cycles());
+  EXPECT_EQ(456u, statistics.cycles());
+  EXPECT_TRUE(statistics.has_task_clock());
+  EXPECT_EQ(0.456, statistics.task_clock());
+
+  // Statistics reporting <not supported> should not appear.
+  parse = perf::parse("<not supported>,cycles");
+  CHECK_SOME(parse);
+
+  ASSERT_TRUE(parse.get().contains(""));
+  statistics = parse.get().get("").get();
+  EXPECT_FALSE(statistics.has_cycles());
+
+  // Statistics reporting <not counted> should be zero.
+  parse = perf::parse("<not counted>,cycles\n<not counted>,task-clock");
+  CHECK_SOME(parse);
+
+  ASSERT_TRUE(parse.get().contains(""));
+  statistics = parse.get().get("").get();
+
+  EXPECT_TRUE(statistics.has_cycles());
+  EXPECT_EQ(0u, statistics.cycles());
+  EXPECT_TRUE(statistics.has_task_clock());
+  EXPECT_EQ(0.0, statistics.task_clock());
+
+  // Check parsing fails.
+  parse = perf::parse("1,cycles\ngarbage");
+  EXPECT_ERROR(parse);
+
+  parse = perf::parse("1,unknown-field");
+  EXPECT_ERROR(parse);
+}
+
+
+TEST_F(PerfTest, ROOT_SamplePid)
+{
+  // TODO(idownes): Replace this with a Subprocess when it supports
+  // DEATHSIG.
+  // Fork a child which we'll run perf against.
+  pid_t pid = fork();
+  ASSERT_GE(pid, 0);
+
+  if (pid == 0) {
+    // Kill ourself if the parent dies to prevent leaking the child.
+    prctl(PR_SET_PDEATHSIG, SIGKILL);
+
+    // Spin child to consume cpu cycles.
+    while (true);
+  }
+
+  // Continue in parent.
+  set<string> events;
+  // Hardware event.
+  events.insert("cycles");
+  // Software event.
+  events.insert("task-clock");
+
+  // Sample the child.
+  Duration duration = Milliseconds(100);
+  Future<mesos::PerfStatistics> statistics =
+    perf::sample(events, pid, duration);
+  AWAIT_READY(statistics);
+
+  // Kill the child and reap it.
+  Future<Option<int>> status = reap(pid);
+  kill(pid, SIGKILL);
+  AWAIT_READY(status);
+
+  // Check the sample timestamp is within the last 5 seconds. This is generous
+  // because there's the process reap delay in addition to the sampling
+  // duration.
+  ASSERT_TRUE(statistics.get().has_timestamp());
+  EXPECT_GT(
+      Seconds(5).secs(), Clock::now().secs() - statistics.get().timestamp());
+  EXPECT_EQ(duration.secs(), statistics.get().duration());
+
+  ASSERT_TRUE(statistics.get().has_cycles());
+
+  // TODO(benh): Some Linux distributions (Ubuntu 14.04) fail to
+  // properly sample 'cycles' with 'perf', so we don't explicitly
+  // check the value here. See MESOS-3082.
+  // EXPECT_LT(0u, statistics.get().cycles());
+
+  ASSERT_TRUE(statistics.get().has_task_clock());
+  EXPECT_LT(0.0, statistics.get().task_clock());
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {