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 {