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 {
