Updated the balloon tests to test the memory threshold. Review: https://reviews.apache.org/r/14044
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/ebba5c10 Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/ebba5c10 Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/ebba5c10 Branch: refs/heads/master Commit: ebba5c109d17fac2a18c16467097846f398e7217 Parents: 8d74239 Author: Benjamin Mahler <bmah...@twitter.com> Authored: Thu Aug 29 11:14:04 2013 -0700 Committer: Benjamin Mahler <bmah...@twitter.com> Committed: Wed Oct 2 12:17:52 2013 -0700 ---------------------------------------------------------------------- src/examples/balloon_executor.cpp | 54 ++++++++++++++++--------------- src/examples/balloon_framework.cpp | 52 +++++++++++++++++------------ src/tests/balloon_framework_test.sh | 11 +++++-- src/tests/cgroups_isolator_tests.cpp | 37 ++++++++++++++++++--- src/tests/examples_tests.cpp | 12 ++++--- src/tests/script.cpp | 17 ++++++++-- src/tests/script.hpp | 39 +++++++++++++++++++--- 7 files changed, 159 insertions(+), 63 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/ebba5c10/src/examples/balloon_executor.cpp ---------------------------------------------------------------------- diff --git a/src/examples/balloon_executor.cpp b/src/examples/balloon_executor.cpp index ad1d861..358afaf 100644 --- a/src/examples/balloon_executor.cpp +++ b/src/examples/balloon_executor.cpp @@ -26,51 +26,50 @@ #include <iostream> #include <string> +#include <vector> #include <mesos/executor.hpp> +#include <stout/bytes.hpp> #include <stout/duration.hpp> #include <stout/numify.hpp> #include <stout/os.hpp> using namespace mesos; - -// The amount of memory in MB each balloon step consumes. -const static size_t BALLOON_STEP_MB = 64; +using std::string; +using std::vector; // This function will increase the memory footprint gradually. The parameter -// limit specifies the upper limit (in MB) of the memory footprint. The -// parameter step specifies the step size (in MB). -static void balloon(size_t limit) +// limit specifies the upper limit of the memory footprint. The +// parameter step specifies the step size. +static void balloon(const Bytes& limit, const Bytes& step) { - size_t chunk = BALLOON_STEP_MB * 1024 * 1024; - for (size_t i = 0; i < limit / BALLOON_STEP_MB; i++) { - std::cout << "Increasing memory footprint by " - << BALLOON_STEP_MB << " MB" << std::endl; + for (size_t i = 0; i < limit.bytes() / step.bytes(); i++) { + std::cout << "Increasing memory footprint by " << step << std::endl; // Allocate page-aligned virtual memory. void* buffer = NULL; - if (posix_memalign(&buffer, getpagesize(), chunk) != 0) { + if (posix_memalign(&buffer, getpagesize(), step.bytes()) != 0) { perror("Failed to allocate page-aligned memory, posix_memalign"); abort(); } // We use mlock and memset here to make sure that the memory // actually gets paged in and thus accounted for. - if (mlock(buffer, chunk) != 0) { + if (mlock(buffer, step.bytes()) != 0) { perror("Failed to lock memory, mlock"); abort(); } - if (memset(buffer, 1, chunk) != buffer) { + if (memset(buffer, 1, step.bytes()) != buffer) { perror("Failed to fill memory, memset"); abort(); } // Try not to increase the memory footprint too fast. - os::sleep(Seconds(1)); + os::sleep(Milliseconds(50)); } } @@ -109,18 +108,21 @@ public: driver->sendStatusUpdate(status); - // Get the balloon limit (in MB). - Try<size_t> limit = numify<size_t>(task.data()); + // Get the balloon step and limit. + vector<string> split = strings::split(task.data(), ","); + + Try<Bytes> step = Bytes::parse(split[0]); + assert(step.isSome()); + + Try<Bytes> limit = Bytes::parse(split[1]); assert(limit.isSome()); - size_t balloonLimit = limit.get(); - // Artificially increase the memory usage gradually. The - // balloonLimit specifies the upper limit. The balloonLimit can be - // larger than the amount of memory allocated to this executor. In - // that case, the isolator (e.g. cgroups) should be able to detect - // that and the task should not be able to reach TASK_FINISHED - // state. - balloon(balloonLimit); + // Artificially increase the memory usage gradually. The limit + // can be larger than the amount of memory allocated to this + // executor. In that case, the isolator (e.g. cgroups) should be + // able to detect that and the task should not be able to reach + // TASK_FINISHED state. + balloon(limit.get(), step.get()); std::cout << "Finishing task " << task.task_id().value() << std::endl; @@ -135,7 +137,7 @@ public: std::cout << "Kill task " << taskId.value() << std::endl; } - virtual void frameworkMessage(ExecutorDriver* driver, const std::string& data) + virtual void frameworkMessage(ExecutorDriver* driver, const string& data) { std::cout << "Framework message: " << data << std::endl; } @@ -145,7 +147,7 @@ public: std::cout << "Shutdown" << std::endl; } - virtual void error(ExecutorDriver* driver, const std::string& message) + virtual void error(ExecutorDriver* driver, const string& message) { std::cout << "Error message: " << message << std::endl; } http://git-wip-us.apache.org/repos/asf/mesos/blob/ebba5c10/src/examples/balloon_framework.cpp ---------------------------------------------------------------------- diff --git a/src/examples/balloon_framework.cpp b/src/examples/balloon_framework.cpp index 53859bc..1df5e22 100644 --- a/src/examples/balloon_framework.cpp +++ b/src/examples/balloon_framework.cpp @@ -28,6 +28,8 @@ #include <mesos/scheduler.hpp> +#include <stout/bytes.hpp> +#include <stout/exit.hpp> #include <stout/numify.hpp> #include <stout/os.hpp> #include <stout/stringify.hpp> @@ -39,16 +41,21 @@ using namespace mesos; using namespace mesos::internal; +using std::string; + // The amount of memory in MB the executor itself takes. -const static size_t EXECUTOR_MEMORY_MB = 64; +const static Bytes EXECUTOR_MEMORY = Megabytes(64); class BalloonScheduler : public Scheduler { public: - BalloonScheduler(const ExecutorInfo& _executor, - size_t _balloonLimit) + BalloonScheduler( + const ExecutorInfo& _executor, + const Bytes& _balloonStep, + const Bytes& _balloonLimit) : executor(_executor), + balloonStep(_balloonStep), balloonLimit(_balloonLimit), taskLaunched(false) {} @@ -82,7 +89,7 @@ public: // We just launch one task. if (!taskLaunched) { double mem = getScalarResource(offer, "mem"); - assert(mem > EXECUTOR_MEMORY_MB); + assert(mem > EXECUTOR_MEMORY.megabytes()); std::vector<TaskInfo> tasks; std::cout << "Starting the task" << std::endl; @@ -92,14 +99,15 @@ public: task.mutable_task_id()->set_value("1"); task.mutable_slave_id()->MergeFrom(offer.slave_id()); task.mutable_executor()->MergeFrom(executor); - task.set_data(stringify<size_t>(balloonLimit)); + task.set_data(stringify(balloonStep) + "," + stringify(balloonLimit)); // Use up all the memory from the offer. Resource* resource; resource = task.add_resources(); resource->set_name("mem"); resource->set_type(Value::SCALAR); - resource->mutable_scalar()->set_value(mem - EXECUTOR_MEMORY_MB); + resource->mutable_scalar()->set_value( + mem - EXECUTOR_MEMORY.megabytes()); tasks.push_back(task); driver->launchTasks(offer.id(), tasks); @@ -161,29 +169,33 @@ public: private: const ExecutorInfo executor; - const size_t balloonLimit; + const Bytes balloonStep; + const Bytes balloonLimit; bool taskLaunched; }; int main(int argc, char** argv) { - if (argc != 3) { - std::cerr << "Usage: " << argv[0] - << " <master> <balloon limit in MB>" << std::endl; - return -1; + if (argc != 4) { + EXIT(1) << "Usage: " << argv[0] + << " <master> <balloon step> <balloon limit>"; + } + + // Parse the balloon step. + Try<Bytes> step = Bytes::parse(argv[2]); + if (step.isError()) { + EXIT(1) << "Balloon memory step is invalid: " << step.error(); } - // Verify the balloon limit. - Try<size_t> limit = numify<size_t>(argv[2]); + // Parse the balloon limit. + Try<Bytes> limit = Bytes::parse(argv[3]); if (limit.isError()) { - std::cerr << "Balloon limit is not a valid number" << std::endl; - return -1; + EXIT(1) << "Balloon memory limit is invalid: " << limit.error(); } - if (limit.get() < EXECUTOR_MEMORY_MB) { - std::cerr << "Please use a balloon limit bigger than " - << EXECUTOR_MEMORY_MB << " MB" << std::endl; + if (limit.get() < EXECUTOR_MEMORY) { + EXIT(1) << "Please use an executor limit smaller than " << EXECUTOR_MEMORY; } // Find this executable's directory to locate executor. @@ -202,9 +214,9 @@ int main(int argc, char** argv) Resource* mem = executor.add_resources(); mem->set_name("mem"); mem->set_type(Value::SCALAR); - mem->mutable_scalar()->set_value(EXECUTOR_MEMORY_MB); + mem->mutable_scalar()->set_value(EXECUTOR_MEMORY.megabytes()); - BalloonScheduler scheduler(executor, limit.get()); + BalloonScheduler scheduler(executor, step.get(), limit.get()); FrameworkInfo framework; framework.set_user(""); // Have Mesos fill in the current user. http://git-wip-us.apache.org/repos/asf/mesos/blob/ebba5c10/src/tests/balloon_framework_test.sh ---------------------------------------------------------------------- diff --git a/src/tests/balloon_framework_test.sh b/src/tests/balloon_framework_test.sh index 8e99456..7229064 100755 --- a/src/tests/balloon_framework_test.sh +++ b/src/tests/balloon_framework_test.sh @@ -3,6 +3,12 @@ # This script runs the balloon framework on a cluster using the cgroups # isolator and checks that the framework returns a status of 1. +if [ $# -ne 3 ]; then + echo "usage: `basename $0` cgroups_oom_buffer balloon_step balloon_limit" + echo "example: `basename $0` 127MB 32MB 1GB" + exit 1 +fi + source ${MESOS_SOURCE_DIR}/support/colors.sh source ${MESOS_SOURCE_DIR}/support/atexit.sh @@ -90,7 +96,8 @@ ${SLAVE} \ --isolation=cgroups \ --cgroups_hierarchy=${TEST_CGROUP_HIERARCHY} \ --cgroups_root=${TEST_CGROUP_ROOT} \ - --resources="cpus:1;mem:96" & + --resources="cpus:1;mem:96" \ + --cgroups_oom_buffer=${1} & SLAVE_PID=${!} echo "${GREEN}Launched slave at ${SLAVE_PID}${NORMAL}" sleep 2 @@ -104,7 +111,7 @@ if [[ ${STATUS} -ne 0 ]]; then fi # The main event! -${BALLOON_FRAMEWORK} localhost:5432 1024 +${BALLOON_FRAMEWORK} localhost:5432 ${2} ${3} STATUS=${?} # Make sure the balloon framework "failed". http://git-wip-us.apache.org/repos/asf/mesos/blob/ebba5c10/src/tests/cgroups_isolator_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/cgroups_isolator_tests.cpp b/src/tests/cgroups_isolator_tests.cpp index 1f5ce76..cdac4a9 100644 --- a/src/tests/cgroups_isolator_tests.cpp +++ b/src/tests/cgroups_isolator_tests.cpp @@ -35,10 +35,39 @@ using namespace mesos::internal::slave; using std::map; -// Run the balloon framework under the cgroups isolator. -TEST_SCRIPT(CgroupsIsolatorTest, - ROOT_CGROUPS_BalloonFramework, - "balloon_framework_test.sh") +// Run the balloon framework under the cgroups isolator with a few +// configurations. + +// No memory buffer on the slave. +// Step with 64MB, limit 1GB. +// This should behave as if there's only a hard threshold. +TEST_SCRIPT_3(CgroupsIsolatorTest, + ROOT_CGROUPS_BalloonFramework_NoBuffer, + "balloon_framework_test.sh", + stringify(Megabytes(0)), + stringify(Megabytes(64)), + stringify(Gigabytes(1))) + + +// 128MB memory buffer on the slave, step with 32 MB, limit 1GB. +// This ensures we'll have time to act on the threshold notification. +TEST_SCRIPT_3(CgroupsIsolatorTest, + ROOT_CGROUPS_BalloonFramework_Threshold, + "balloon_framework_test.sh", + stringify(Megabytes(128)), + stringify(Megabytes(32)), + stringify(Gigabytes(1))) + + +// 1MB memory buffer on the slave, step with 64 MB, limit 1GB. +// This ensures the OOM occurs before we can act on the threshold +// notification. +TEST_SCRIPT_3(CgroupsIsolatorTest, + ROOT_CGROUPS_BalloonFramework_OOM, + "balloon_framework_test.sh", + stringify(Megabytes(1)), + stringify(Megabytes(128)), + stringify(Gigabytes(1))) #define GROW_USAGE(delta, cpuset, usage) \ http://git-wip-us.apache.org/repos/asf/mesos/blob/ebba5c10/src/tests/examples_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/examples_tests.cpp b/src/tests/examples_tests.cpp index 28ff0f3..d7429bb 100644 --- a/src/tests/examples_tests.cpp +++ b/src/tests/examples_tests.cpp @@ -22,14 +22,16 @@ // Run each of the sample frameworks in local mode. -TEST_SCRIPT(ExamplesTest, TestFramework, "test_framework_test.sh") -TEST_SCRIPT(ExamplesTest, NoExecutorFramework, "no_executor_framework_test.sh") +TEST_SCRIPT_0(ExamplesTest, TestFramework, "test_framework_test.sh") +TEST_SCRIPT_0(ExamplesTest, + NoExecutorFramework, + "no_executor_framework_test.sh") #ifdef MESOS_HAS_JAVA -TEST_SCRIPT(ExamplesTest, JavaFramework, "java_framework_test.sh") -TEST_SCRIPT(ExamplesTest, JavaException, "java_exception_test.sh") +TEST_SCRIPT_0(ExamplesTest, JavaFramework, "java_framework_test.sh") +TEST_SCRIPT_0(ExamplesTest, JavaException, "java_exception_test.sh") #endif #ifdef MESOS_HAS_PYTHON -TEST_SCRIPT(ExamplesTest, PythonFramework, "python_framework_test.sh") +TEST_SCRIPT_0(ExamplesTest, PythonFramework, "python_framework_test.sh") #endif http://git-wip-us.apache.org/repos/asf/mesos/blob/ebba5c10/src/tests/script.cpp ---------------------------------------------------------------------- diff --git a/src/tests/script.cpp b/src/tests/script.cpp index e72eea6..04150ba 100644 --- a/src/tests/script.cpp +++ b/src/tests/script.cpp @@ -23,6 +23,7 @@ #include <sys/wait.h> // For wait (and associated macros). #include <string> +#include <vector> #include <stout/check.hpp> #include <stout/os.hpp> @@ -34,13 +35,16 @@ #include "tests/script.hpp" using std::string; +using std::vector; namespace mesos { namespace internal { namespace tests { -void execute(const string& script) +void execute(const string& script, const vector<string>& arguments) { + CHECK_LT(arguments.size(), static_cast<size_t>(ARG_MAX)); + // Create a temporary directory for the test. Try<string> directory = environment->mkdtemp(); @@ -104,8 +108,17 @@ void execute(const string& script) os::setenv("MESOS_WEBUI_DIR", path::join(flags.source_dir, "src", "webui")); os::setenv("MESOS_LAUNCHER_DIR", path::join(flags.build_dir, "src")); + // Construct the argument array. + const char** args = (const char**) new char*[arguments.size() + 1]; + args[0] = path.get().c_str(); + size_t index = 1; + foreach (const string& argument, arguments) { + args[index++] = argument.c_str(); + } + args[arguments.size() + 1] = NULL; + // Now execute the script. - execl(path.get().c_str(), path.get().c_str(), (char*) NULL); + execv(path.get().c_str(), (char* const*) args); std::cerr << "Failed to execute '" << script << "': " << strerror(errno) << std::endl; http://git-wip-us.apache.org/repos/asf/mesos/blob/ebba5c10/src/tests/script.hpp ---------------------------------------------------------------------- diff --git a/src/tests/script.hpp b/src/tests/script.hpp index f0c71e1..980df20 100644 --- a/src/tests/script.hpp +++ b/src/tests/script.hpp @@ -21,12 +21,19 @@ #include <gtest/gtest.h> +#include <string> +#include <vector> + +#include <stout/preprocessor.hpp> + namespace mesos { namespace internal { namespace tests { // Helper used by TEST_SCRIPT to execute the script. -void execute(const std::string& script); +void execute( + const std::string& script, + const std::vector<std::string>& arguments = std::vector<std::string>()); } // namespace tests { } // namespace internal { @@ -37,9 +44,33 @@ void execute(const std::string& script); // script in temporary directory and pipe its output to '/dev/null' // unless the verbose option is specified. The "test" passes if the // script returns 0. -#define TEST_SCRIPT(test_case_name, test_name, script) \ - TEST(test_case_name, test_name) { \ - mesos::internal::tests::execute(script); \ +#define TEST_SCRIPT_0(test_case_name, test_name, script) \ + TEST(test_case_name, test_name) { \ + mesos::internal::tests::execute(script); \ + } + +#define TEST_SCRIPT_1(test_case_name, test_name, script, arg1) \ + TEST(test_case_name, test_name) { \ + std::vector<std::string> arguments; \ + arguments.push_back(arg1); \ + mesos::internal::tests::execute(script, arguments); \ + } + +#define TEST_SCRIPT_2(test_case_name, test_name, script, arg1, arg2) \ + TEST(test_case_name, test_name) { \ + std::vector<std::string> arguments; \ + arguments.push_back(arg1); \ + arguments.push_back(arg2); \ + mesos::internal::tests::execute(script, arguments); \ + } + +#define TEST_SCRIPT_3(test_case_name, test_name, script, arg1, arg2, arg3) \ + TEST(test_case_name, test_name) { \ + std::vector<std::string> arguments; \ + arguments.push_back(arg1); \ + arguments.push_back(arg2); \ + arguments.push_back(arg3); \ + mesos::internal::tests::execute(script, arguments); \ } #endif // __TESTS_SCRIPT_HPP__