├── .clang-format ├── .gitignore ├── .gitmodules ├── Makefile ├── README.md ├── hooks ├── pre-commit ├── pre-commit-10build └── pre-commit-20check ├── scripts ├── cpplint.py └── installHooks.sh └── src ├── ArbiterClientShim.cc ├── ArbiterClientShim.h ├── CodeLocation.cc ├── CodeLocation.h ├── CoreArbiterClient.cc ├── CoreArbiterClient.h ├── CoreArbiterClientMain.cc ├── CoreArbiterClientTest.cc ├── CoreArbiterCommon.h ├── CoreArbiterRampDownTest.cc ├── CoreArbiterRequestTest.cc ├── CoreArbiterServer.cc ├── CoreArbiterServer.h ├── CoreArbiterServerMain.cc ├── CoreArbiterServerTest.cc ├── Logger.cc ├── Logger.h ├── MockSyscall.h ├── Semaphore.h ├── Syscall.h └── mkdir_p.cc /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | IndentWidth: 4 4 | IndentCaseLabels: true 5 | AccessModifierOffset: -2 6 | AllowShortBlocksOnASingleLine: false 7 | AllowShortIfStatementsOnASingleLine: false 8 | AllowShortLoopsOnASingleLine: false 9 | --- 10 | Language: Cpp 11 | # Force pointers to the type for C++. 12 | DerivePointerAlignment: false 13 | PointerAlignment: Left 14 | AlwaysBreakAfterReturnType: TopLevelDefinitions 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | *.smod 19 | 20 | # Compiled Static libraries 21 | *.lai 22 | *.la 23 | *.a 24 | *.lib 25 | 26 | # Executables 27 | *.exe 28 | *.out 29 | *.app 30 | client 31 | server 32 | 33 | # Misc files 34 | tags 35 | 36 | # Vim swap files 37 | .*.swp 38 | 39 | # Generated files 40 | obj/ 41 | lib/ 42 | include/ 43 | bin/ 44 | 45 | # Patch files 46 | *.patch 47 | *.rej 48 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlatformLab/CoreArbiter/6c7b6e3992c7996267ccca8a338e73b59a6d36a1/.gitmodules -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CXX ?= g++ 2 | CCFLAGS=-g -Wall -Wformat=2 -Wextra -Wwrite-strings \ 3 | -Wno-unused-parameter -Wmissing-format-attribute -Wno-non-template-friend \ 4 | -Woverloaded-virtual -Wcast-qual -Wcast-align -Wconversion -fomit-frame-pointer \ 5 | -std=c++11 -fPIC -O3 $(EXTRA_CXXFLAGS) 6 | 7 | # Output directories 8 | OBJECT_DIR = obj 9 | SRC_DIR = src 10 | INCLUDE_DIR = include 11 | LIB_DIR = lib 12 | BIN_DIR = bin 13 | 14 | # Depenencies 15 | PERFUTILS=../PerfUtils 16 | INCLUDE=-I$(PERFUTILS)/include 17 | LIBS=$(PERFUTILS)/lib/libPerfUtils.a -lpcrecpp -pthread 18 | 19 | # Stuff needed for make check 20 | TOP := $(shell echo $${PWD-`pwd`}) 21 | ifndef CHECK_TARGET 22 | CHECK_TARGET=$$(find $(SRC_DIR) '(' -name '*.h' -or -name '*.cc' ')' -not -path '$(TOP)/googletest/*' ) 23 | endif 24 | 25 | OBJECT_NAMES := CoreArbiterServer.o CoreArbiterClient.o mkdir_p.o Logger.o CodeLocation.o ArbiterClientShim.o 26 | 27 | OBJECTS = $(patsubst %,$(OBJECT_DIR)/%,$(OBJECT_NAMES)) 28 | HEADERS= $(shell find src -name '*.h') 29 | 30 | ifeq ($(MAKECMDGOALS),clean) 31 | DEP= 32 | else 33 | DEP=$(OBJECTS:.o=.d) 34 | endif # ($(MAKECMDGOALS),clean) 35 | 36 | SERVER_BIN = $(OBJECT_DIR)/coreArbiterServer 37 | CLIENT_BIN = $(OBJECT_DIR)/client 38 | 39 | install: $(SERVER_BIN) $(CLIENT_BIN) 40 | mkdir -p $(BIN_DIR) $(LIB_DIR) $(INCLUDE_DIR)/CoreArbiter 41 | cp $(HEADERS) $(INCLUDE_DIR)/CoreArbiter 42 | cp $(SERVER_BIN) $(CLIENT_BIN) bin 43 | cp $(OBJECT_DIR)/libCoreArbiter.a lib 44 | 45 | $(SERVER_BIN): $(OBJECT_DIR)/CoreArbiterServerMain.o $(OBJECT_DIR)/libCoreArbiter.a 46 | $(CXX) $(LDFLAGS) $(CCFLAGS) -o $@ $^ $(LIBS) 47 | 48 | $(CLIENT_BIN): $(OBJECT_DIR)/CoreArbiterClientMain.o $(OBJECT_DIR)/libCoreArbiter.a 49 | $(CXX) $(LDFLAGS) $(CCFLAGS) -o $@ $^ $(LIBS) 50 | 51 | $(OBJECT_DIR)/libCoreArbiter.a: $(OBJECTS) 52 | ar rcs $@ $^ 53 | 54 | -include $(DEP) 55 | 56 | $(OBJECT_DIR)/%.d: $(SRC_DIR)/%.cc | $(OBJECT_DIR) 57 | $(CXX) $(INCLUDE) $(CCFLAGS) $< -MM -MT $(@:.d=.o) > $@ 58 | 59 | $(OBJECT_DIR)/%.o: $(SRC_DIR)/%.cc $(HEADERS) | $(OBJECT_DIR) 60 | $(CXX) $(INCLUDE) $(CCFLAGS) -c $< -o $@ 61 | 62 | $(OBJECT_DIR): 63 | mkdir -p $(OBJECT_DIR) 64 | 65 | check: 66 | scripts/cpplint.py --filter=-runtime/threadsafe_fn,-readability/streams,-whitespace/blank_line,-whitespace/braces,-whitespace/comments,-runtime/arrays,-build/include_what_you_use,-whitespace/semicolon $(CHECK_TARGET) 67 | ! grep '.\{81\}' $(SRC_DIR)/*.h $(SRC_DIR)/*.cc 68 | 69 | ################################################################################ 70 | # Test Targets 71 | 72 | GTEST_DIR=../googletest/googletest 73 | TEST_LIBS=-Lobj/ -lCoreArbiter $(OBJECT_DIR)/libgtest.a 74 | INCLUDE+=-I${GTEST_DIR}/include 75 | 76 | test: $(OBJECT_DIR)/CoreArbiterServerTest $(OBJECT_DIR)/CoreArbiterClientTest $(OBJECT_DIR)/CoreArbiterRequestTest $(OBJECT_DIR)/CoreArbiterRampDownTest 77 | $(OBJECT_DIR)/CoreArbiterServerTest 78 | $(OBJECT_DIR)/CoreArbiterClientTest 79 | # The following test is built but must be run manually for now. 80 | # $(OBJECT_DIR)/CoreArbiterRequestTest 81 | 82 | $(OBJECT_DIR)/CoreArbiterServerTest: $(OBJECT_DIR)/CoreArbiterServerTest.o $(OBJECT_DIR)/libgtest.a $(OBJECT_DIR)/libCoreArbiter.a 83 | $(CXX) $(LDFLAGS) $(INCLUDE) $(CCFLAGS) $< $(GTEST_DIR)/src/gtest_main.cc $(TEST_LIBS) $(LIBS) -o $@ 84 | 85 | $(OBJECT_DIR)/CoreArbiterClientTest: $(OBJECT_DIR)/CoreArbiterClientTest.o $(OBJECT_DIR)/libgtest.a $(OBJECT_DIR)/libCoreArbiter.a 86 | $(CXX) $(LDFLAGS) $(INCLUDE) $(CCFLAGS) $< $(GTEST_DIR)/src/gtest_main.cc $(TEST_LIBS) $(LIBS) -o $@ 87 | 88 | $(OBJECT_DIR)/CoreArbiterRequestTest: $(OBJECT_DIR)/CoreArbiterRequestTest.o $(OBJECT_DIR)/libCoreArbiter.a 89 | $(CXX) $(LDFLAGS) $(INCLUDE) $(CCFLAGS) $^ $(LIBS) -o $@ 90 | 91 | $(OBJECT_DIR)/CoreArbiterRampDownTest: $(OBJECT_DIR)/CoreArbiterRampDownTest.o $(OBJECT_DIR)/libCoreArbiter.a 92 | $(CXX) $(LDFLAGS) $(INCLUDE) $(CCFLAGS) $^ $(LIBS) -o $@ 93 | 94 | $(OBJECT_DIR)/libgtest.a: 95 | $(CXX) -I${GTEST_DIR}/include -I${GTEST_DIR} \ 96 | -pthread -c ${GTEST_DIR}/src/gtest-all.cc \ 97 | -o $(OBJECT_DIR)/gtest-all.o 98 | ar -rv $(OBJECT_DIR)/libgtest.a $(OBJECT_DIR)/gtest-all.o 99 | ################################################################################ 100 | 101 | clean: 102 | rm -rf $(OBJECT_DIR) $(BIN_DIR) $(INCLUDE_DIR) $(LIB_DIR) 103 | 104 | .PHONY: install clean 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CoreArbiter 2 | 3 | The Core Arbiter is the core allocation component of Arachne. It allows an application to request and obtain managed cores in the form of threads running exclusively on a core. 4 | To start the CoreArbiter's server process, it is sufficient to build and run it as root. 5 | 6 | ``` 7 | make 8 | sudo bin/coreArbiterServer 9 | ``` 10 | -------------------------------------------------------------------------------- /hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | fail=0 3 | for hook in hooks/pre-commit-*; do 4 | if [ -x $hook ]; then 5 | echo "Running $hook..." 6 | $hook 7 | status=$? 8 | if [ $status -ne 0 ]; then 9 | echo "$hook failed with status $status" 10 | fail=1 11 | fi 12 | else 13 | echo "WARNING: Skipping $hook since it's not executable." 14 | fi 15 | done 16 | exit $fail 17 | -------------------------------------------------------------------------------- /hooks/pre-commit-10build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | exec make -j8 --silent > /dev/null 3 | -------------------------------------------------------------------------------- /hooks/pre-commit-20check: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | exec make --silent check >/dev/null 2>&1 3 | -------------------------------------------------------------------------------- /scripts/installHooks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | hookDir=$(git rev-parse --git-dir)/hooks 5 | 6 | mkdir -p $hookDir 7 | cp hooks/* $hookDir 8 | -------------------------------------------------------------------------------- /src/ArbiterClientShim.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017 Stanford University 2 | * 3 | * Permission to use, copy, modify, and distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | #include "ArbiterClientShim.h" 16 | #include 17 | #include 18 | 19 | namespace Arachne { 20 | 21 | /** 22 | * Implements functionality of CoreArbiterClient::blockUntilCoreAvailable. 23 | **/ 24 | int 25 | ArbiterClientShim::blockUntilCoreAvailable() { 26 | static std::atomic nextCoreId(0); 27 | static thread_local int coreId = nextCoreId.fetch_add(1); 28 | waitingForAvailableCore.wait(); 29 | return coreId; 30 | } 31 | 32 | /** 33 | * Implements functionality of CoreArbiterClient::mustReleaseCore. 34 | **/ 35 | bool 36 | ArbiterClientShim::mustReleaseCore() { 37 | // Avoid acquiring lock if possible. 38 | if (currentRequestedCores >= currentCores) 39 | return false; 40 | 41 | std::lock_guard guard(shimLock); 42 | if (currentRequestedCores < currentCores) { 43 | currentCores--; 44 | return true; 45 | } 46 | return false; 47 | } 48 | 49 | /** 50 | * Implements functionality of CoreArbiterClient::setRequestedCores. 51 | * 52 | * \param numCores 53 | * Same as in CoreArbiterClient::setRequestedCores. 54 | */ 55 | void 56 | ArbiterClientShim::setRequestedCores(std::vector numCores) { 57 | uint32_t sum = 0; 58 | for (uint32_t i : numCores) 59 | sum += i; 60 | currentRequestedCores = sum; 61 | 62 | std::lock_guard guard(shimLock); 63 | if (currentRequestedCores > currentCores) { 64 | uint64_t diff = currentRequestedCores - currentCores; 65 | for (uint64_t i = 0; i < diff; i++) 66 | waitingForAvailableCore.notify(); 67 | currentCores.store(currentRequestedCores); 68 | } 69 | } 70 | 71 | /** 72 | * Implements functionality of CoreArbiterClient::unregisterThread. 73 | **/ 74 | void 75 | ArbiterClientShim::unregisterThread() { 76 | // Because there is no server, this function is a no-op. 77 | } 78 | } // namespace Arachne 79 | -------------------------------------------------------------------------------- /src/ArbiterClientShim.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017 Stanford University 2 | * 3 | * Permission to use, copy, modify, and distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #ifndef ARBITER_CLIENT_SHIM_H 17 | #define ARBITER_CLIENT_SHIM_H 18 | 19 | #include 20 | #include 21 | 22 | #include "CoreArbiterClient.h" 23 | #include "Semaphore.h" 24 | 25 | namespace Arachne { 26 | 27 | /** 28 | * This class functions as a shim, or alternative, for the CoreArbiter 29 | * client so that Arachne can run without the Core Arbiter when the 30 | * arbiter is deactivated. 31 | */ 32 | class ArbiterClientShim : public CoreArbiter::CoreArbiterClient { 33 | public: 34 | int blockUntilCoreAvailable(); 35 | bool mustReleaseCore(); 36 | void setRequestedCores(std::vector numCores); 37 | void unregisterThread(); 38 | void reset() { 39 | currentRequestedCores = 0; 40 | currentCores = 0; 41 | waitingForAvailableCore.reset(); 42 | } 43 | 44 | /** 45 | * Allow access to singleton. Enforce single instance. 46 | **/ 47 | static ArbiterClientShim* getInstance() { 48 | static ArbiterClientShim instance; 49 | return &instance; 50 | } 51 | 52 | private: 53 | /** 54 | * Private constructor for ArbiterClientShim. 55 | */ 56 | ArbiterClientShim() 57 | : CoreArbiter::CoreArbiterClient(""), 58 | waitingForAvailableCore(), 59 | currentRequestedCores(), 60 | currentCores(), 61 | shimLock() {} 62 | 63 | /** 64 | * Threads block on this semaphor while waiting for a core 65 | * to become available. 66 | */ 67 | ::Semaphore waitingForAvailableCore; 68 | 69 | /** 70 | * The current number of cores this application prefers to have. 71 | */ 72 | std::atomic currentRequestedCores; 73 | 74 | /** 75 | * The current cores held by the application. 76 | */ 77 | std::atomic currentCores; 78 | 79 | /** 80 | * Synchronize between different threads trying to compare 81 | * currentRequestedCores and currentCores, or writing currentCores. 82 | */ 83 | std::mutex shimLock; 84 | /* 85 | * NB: Since shimLock is taken inside the Arachne dispatch() method 86 | * which may be polling on an unocupied context, it is not safe to for the 87 | * lock to enter dispatch() again. This is because a wakeup flag set up by 88 | * a createThread() may get wiped out when the nested dispatch call 89 | * returns. 90 | */ 91 | }; 92 | 93 | } // namespace Arachne 94 | #endif // ARBITER_CLIENT_SHIM_H 95 | -------------------------------------------------------------------------------- /src/CodeLocation.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2017 Stanford University 2 | * 3 | * Permission to use, copy, modify, and distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #include "CodeLocation.h" 17 | #include 18 | #include 19 | 20 | namespace CoreArbiter { 21 | 22 | namespace { 23 | 24 | /** 25 | * Return the number of characters of __FILE__ that make up the path prefix. 26 | * That is, __FILE__ plus this value will be the relative path from the top 27 | * directory of the RAMCloud repo. 28 | */ 29 | int 30 | length__FILE__Prefix() { 31 | const char* start = __FILE__; 32 | const char* match = strstr(__FILE__, "src/CodeLocation.cc"); 33 | assert(match != NULL); 34 | return downCast(match - start); 35 | } 36 | 37 | } // anonymous namespace 38 | 39 | /** 40 | * Return the base name of the file (i.e., only the last component of the 41 | * file name, omitting any preceding directories). 42 | */ 43 | const char* 44 | CodeLocation::baseFileName() const { 45 | const char* lastSlash = strrchr(file, '/'); 46 | if (lastSlash == NULL) { 47 | return file; 48 | } 49 | return lastSlash + 1; 50 | } 51 | 52 | std::string 53 | CodeLocation::relativeFile() const { 54 | static int lengthFilePrefix = length__FILE__Prefix(); 55 | // Remove the prefix only if it matches that of __FILE__. This check is 56 | // needed in case someone compiles different files using different paths. 57 | if (strncmp(file, __FILE__, lengthFilePrefix) == 0) 58 | return std::string(file + lengthFilePrefix); 59 | else 60 | return std::string(file); 61 | } 62 | 63 | /** 64 | * Return the name of the function, qualified by its surrounding classes and 65 | * namespaces. Note that this strips off the RAMCloud namespace to produce 66 | * shorter strings. 67 | * 68 | * Beware: this method is really really slow (10-20 microseconds); we no 69 | * longer use it in log messages because it wastes so much time. 70 | */ 71 | std::string 72 | CodeLocation::qualifiedFunction() const { 73 | std::string ret; 74 | const std::string pattern( 75 | format("\\s(?:RAMCloud::)?(\\S*\\b%s)\\(", function)); 76 | if (pcrecpp::RE(pattern).PartialMatch(prettyFunction, &ret)) 77 | return ret; 78 | else // shouldn't happen 79 | return function; 80 | } 81 | 82 | // Utility functions used for formatting. 83 | 84 | /// A safe version of sprintf. 85 | std::string 86 | format(const char* format, ...) { 87 | va_list ap; 88 | va_start(ap, format); 89 | std::string s = vformat(format, ap); 90 | va_end(ap); 91 | return s; 92 | } 93 | 94 | /// A safe version of vprintf. 95 | std::string 96 | vformat(const char* format, va_list ap) { 97 | std::string s; 98 | 99 | // We're not really sure how big of a buffer will be necessary. 100 | // Try 1K, if not the return value will tell us how much is necessary. 101 | int bufSize = 1024; 102 | while (true) { 103 | char buf[bufSize]; 104 | // vsnprintf trashes the va_list, so copy it first 105 | va_list aq; 106 | __va_copy(aq, ap); 107 | int r = vsnprintf(buf, bufSize, format, aq); 108 | assert(r >= 0); // old glibc versions returned -1 109 | if (r < bufSize) { 110 | s = buf; 111 | break; 112 | } 113 | bufSize = r + 1; 114 | } 115 | 116 | return s; 117 | } 118 | 119 | } // namespace CoreArbiter 120 | -------------------------------------------------------------------------------- /src/CodeLocation.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2011-2017 Stanford University 2 | * 3 | * Permission to use, copy, modify, and distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #ifndef CORE_ARBITER_CODELOCATION_H 17 | #define CORE_ARBITER_CODELOCATION_H 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | namespace CoreArbiter { 24 | 25 | // Utility functions used. 26 | std::string format(const char* format, ...); 27 | 28 | /** 29 | * Describes the location of a line of code. 30 | * You can get one of these with #HERE. 31 | */ 32 | struct CodeLocation { 33 | /// Called by #HERE only. 34 | CodeLocation(const char* file, const uint32_t line, const char* function, 35 | const char* prettyFunction) 36 | : file(file), 37 | line(line), 38 | function(function), 39 | prettyFunction(prettyFunction) {} 40 | std::string str() const { 41 | return format("%s at %s:%d", qualifiedFunction().c_str(), 42 | relativeFile().c_str(), line); 43 | } 44 | const char* baseFileName() const; 45 | std::string relativeFile() const; 46 | std::string qualifiedFunction() const; 47 | 48 | /// __FILE__ 49 | const char* file; 50 | /// __LINE__ 51 | uint32_t line; 52 | /// __func__ 53 | const char* function; 54 | /// __PRETTY_FUNCTION__ 55 | const char* prettyFunction; 56 | }; 57 | 58 | /** 59 | * Constructs a #CodeLocation describing the line from where it is used. 60 | */ 61 | #define HERE \ 62 | CoreArbiter::CodeLocation(__FILE__, __LINE__, __func__, __PRETTY_FUNCTION__) 63 | 64 | /** 65 | * Cast one size of int down to another one. 66 | * Asserts that no precision is lost at runtime. 67 | */ 68 | template 69 | Small 70 | downCast(const Large& large) { 71 | Small small = static_cast(large); 72 | // The following comparison (rather than "large==small") allows 73 | // this method to convert between signed and unsigned values. 74 | assert(large - small == 0); 75 | return small; 76 | } 77 | 78 | std::string format(const char* format, ...) 79 | __attribute__((format(printf, 1, 2))); 80 | std::string vformat(const char* format, va_list ap) 81 | __attribute__((format(printf, 1, 0))); 82 | 83 | } // namespace CoreArbiter 84 | 85 | #endif // RAMCLOUD_CODELOCATION_H 86 | -------------------------------------------------------------------------------- /src/CoreArbiterClient.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017 Stanford University 2 | * 3 | * Permission to use, copy, modify, and distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #include "CoreArbiterClient.h" 29 | #include "Logger.h" 30 | #include "PerfUtils/TimeTrace.h" 31 | #include "PerfUtils/Util.h" 32 | 33 | using PerfUtils::TimeTrace; 34 | 35 | // Uncomment the following line to enable time traces. 36 | // #define TIME_TRACE 1 37 | 38 | namespace CoreArbiter { 39 | 40 | thread_local int CoreArbiterClient::serverSocket = -1; 41 | thread_local int CoreArbiterClient::coreId = -1; 42 | 43 | static Syscall defaultSyscall; 44 | Syscall* CoreArbiterClient::sys = &defaultSyscall; 45 | 46 | bool CoreArbiterClient::testingSkipConnectionSetup = false; 47 | 48 | // Provides a cleaner way of invoking TimeTrace::record, with the code 49 | // conditionally compiled in or out by the TIME_TRACE #ifdef. Arguments 50 | // are made uint64_t (as opposed to uin32_t) so the caller doesn't have to 51 | // frequently cast their 64-bit arguments into uint32_t explicitly: we will 52 | // help perform the casting internally. 53 | static inline void 54 | timeTrace(const char* format, uint64_t arg0 = 0, uint64_t arg1 = 0, 55 | uint64_t arg2 = 0, uint64_t arg3 = 0) { 56 | #if TIME_TRACE 57 | TimeTrace::record(format, uint32_t(arg0), uint32_t(arg1), uint32_t(arg2), 58 | uint32_t(arg3)); 59 | #endif 60 | } 61 | 62 | /** 63 | * Private constructor because CoreArbiterClient is a singleton class. The 64 | * constructor itself doesn't do anything except establish the path to the 65 | * server. A connection with the server is established the first time a thread 66 | * needs to communicate with it (i.e., calls setRequestedCores or 67 | * shouldReleaseCore). 68 | * 69 | * The server is expected to be running before the client is initialized. 70 | * 71 | * \param serverSocketPath 72 | * The path to the socket that the server is listening 73 | * for connections on. 74 | */ 75 | CoreArbiterClient::CoreArbiterClient(std::string serverSocketPath) 76 | : mutex(), 77 | numOwnedCores(0), 78 | numBlockedThreads(0), 79 | serverSocketPath(serverSocketPath), 80 | processSharedMemFd(-1), 81 | globalSharedMemFd(-1) {} 82 | 83 | CoreArbiterClient::~CoreArbiterClient() { 84 | if (!testingSkipConnectionSetup) { 85 | sys->close(processSharedMemFd); 86 | sys->close(globalSharedMemFd); 87 | } 88 | } 89 | 90 | /** 91 | * Requests a specified number of cores at various priority levels from the 92 | * server. The server expects NUM_PRIORITIES priority levels; a request 93 | * specifying more or fewer levels is considered an error. For example, if the 94 | * application wants 2 threads at priority 1 and 1 thread at priority 2 (with 95 | * 0-indexed priorities), it should send: 96 | * 0 2 1 0 0 0 0 0 97 | * Lower indexes have higher priority. Priorities are on a per-process basis. 98 | * One thread calling setRequestedCores will changed the desired number of 99 | * cores for all threads in its process. 100 | * 101 | * This request for cores is handled asynchronously by the server. See 102 | * blockUntilCoreAvailable() and getnumOwnedCores() for how to actually place 103 | * a thread on a core and check how many cores the process currently owns. 104 | * 105 | * Throws a ClientException on error. 106 | * 107 | * \param numCores 108 | * A vector specifying the number of cores requested at every priority 109 | * level. The vector must of NUM_PRIORITIES entries. Lower indexes have 110 | * higher priority. 111 | */ 112 | void 113 | CoreArbiterClient::setRequestedCores(std::vector numCores) { 114 | timeTrace("CLIENT: setRequestedCores invoked"); 115 | if (numCores.size() != NUM_PRIORITIES) { 116 | std::string err = "Core request must have " + 117 | std::to_string(NUM_PRIORITIES) + " priorities"; 118 | LOG(ERROR, "%s", err.c_str()); 119 | throw ClientException(err); 120 | } 121 | 122 | if (serverSocket < 0) { 123 | // This thread has not yet registered with the server 124 | createNewServerConnection(); 125 | } 126 | 127 | std::stringstream result; 128 | std::copy(numCores.begin(), numCores.end(), 129 | std::ostream_iterator(result, " ")); 130 | 131 | LOG(NOTICE, "Core request: %s", result.str().c_str()); 132 | 133 | uint8_t coreRequestMsg = CORE_REQUEST; 134 | sendData(serverSocket, &coreRequestMsg, sizeof(uint8_t), 135 | "Error sending core request prefix"); 136 | 137 | sendData(serverSocket, &numCores[0], sizeof(uint32_t) * NUM_PRIORITIES, 138 | "Error sending core request priorities"); 139 | } 140 | 141 | /** 142 | * Returns true if the server has requested that this client release a core. It 143 | * will only return true once per core that should be released. The caller is 144 | * obligated to ensure that some thread on an exclusive core calls 145 | * blockUntilCoreAvailable() for every time this method returns true. This 146 | * method should be called periodically, as the server will move an 147 | * uncooperative process's threads to an unmanaged core after RELEASE_TIMEOUT_MS 148 | * milliseconds. 149 | */ 150 | bool 151 | CoreArbiterClient::mustReleaseCore() { 152 | if (serverSocket < 0) { 153 | // This thread hasn't established a connection with the server yet. 154 | createNewServerConnection(); 155 | } 156 | 157 | bool coreReleaseRequested = processStats->threadCommunicationBlocks[coreId] 158 | .coreReleaseRequested.load(); 159 | if (coreReleaseRequested) { 160 | LOG(NOTICE, "Core release requested"); 161 | timeTrace("CLIENT: Detected that a core release was requested"); 162 | } 163 | return coreReleaseRequested; 164 | } 165 | 166 | /** 167 | * Returns true if this process has a thread that was previously running 168 | * on a managed core but was moved to the unmanaged core. This happens when 169 | * a preempted thread does not release its core soon enough. 170 | */ 171 | bool 172 | CoreArbiterClient::threadPreempted() { 173 | return processStats->preemptedCount > processStats->unpreemptedCount; 174 | } 175 | 176 | /** 177 | * This method should be called by a thread that wants to run exclusively on a 178 | * managed core. It blocks the thread and does not return until it has been 179 | * placed on a core. In general it is safe to call blockUntilCoreAvailable() 180 | * before setRequestedCores(), but if a process calls blockUntilCoreAvailable() 181 | * on all of its threads it cannot get any work done, including calling 182 | * setRequestedCores(). At most the number of threads specified by 183 | * setRequestedCores() will be woken up from a call to 184 | * blockUntilCoreAvailable(). 185 | * 186 | * Throws a ClientException on error. 187 | * 188 | * \return 189 | * The core ID of the core that this thread has woken up on. 190 | */ 191 | int 192 | CoreArbiterClient::blockUntilCoreAvailable() { 193 | if (serverSocket < 0) { 194 | // This thread has not yet registered with the server 195 | createNewServerConnection(); 196 | } else if (coreId >= 0) { 197 | // This thread currently has exclusive access to a core. We need to 198 | // check whether it should be blocking. 199 | if (!processStats->threadCommunicationBlocks[coreId] 200 | .coreReleaseRequested.load()) { 201 | LOG(WARNING, 202 | "Not blocking thread %d because its process has not " 203 | "been asked to give up a core\n", 204 | sys->gettid()); 205 | return coreId; 206 | } else { 207 | numOwnedCores--; 208 | } 209 | } 210 | timeTrace("CLIENT: blockUntilCoreAvailable about to release a core"); 211 | 212 | numBlockedThreads++; 213 | 214 | uint8_t threadBlockMsg = THREAD_BLOCK; 215 | if (sys->send(serverSocket, &threadBlockMsg, sizeof(uint8_t), 0) < 0) { 216 | numBlockedThreads--; 217 | 218 | std::string err = "Error sending block message"; 219 | std::string fullErrStr = err + ": " + std::string(strerror(errno)); 220 | LOG(ERROR, "%s", fullErrStr.c_str()); 221 | throw ClientException(err); 222 | } 223 | 224 | LOG(NOTICE, "Thread %d is blocking until message received from server", 225 | sys->gettid()); 226 | coreId = -1; 227 | readData(serverSocket, &coreId, sizeof(int), 228 | "Error receiving core ID from server"); 229 | 230 | LOG(NOTICE, "Thread %d woke up on core %d.", sys->gettid(), coreId); 231 | numOwnedCores++; 232 | numBlockedThreads--; 233 | 234 | timeTrace("CLIENT: blockUntilCoreAvailable just obtained a core"); 235 | return coreId; 236 | } 237 | 238 | /** 239 | * Tells the server that this thread no longer wishes to run on managed cores. 240 | * This should always be called before a thread exits to ensure that the server 241 | * doesn't keep stale threads on cores. 242 | */ 243 | void 244 | CoreArbiterClient::unregisterThread() { 245 | if (serverSocket < 0) { 246 | LOG(WARNING, 247 | "Cannot unregister a thread that was not previously " 248 | "registered\n"); 249 | return; 250 | } 251 | 252 | LOG(NOTICE, "Unregistering thread %d", sys->gettid()); 253 | 254 | // Closing this socket alerts the server, which will clean up this thread's 255 | // state 256 | if (sys->close(serverSocket) < 0) { 257 | LOG(ERROR, "Error closing socket: %s", strerror(errno)); 258 | } 259 | } 260 | 261 | int 262 | CoreArbiterClient::getCoreId() { 263 | return coreId; 264 | } 265 | 266 | // -- methods for testing 267 | 268 | /** 269 | * Returns the number of threads this process owns that are running on a managed 270 | * core, from the server's perspective. 271 | */ 272 | uint32_t 273 | CoreArbiterClient::getNumOwnedCoresFromServer() { 274 | if (serverSocket < 0) { 275 | createNewServerConnection(); 276 | } 277 | 278 | return processStats->numOwnedCores.load(); 279 | } 280 | 281 | /** 282 | * Returns the number of threads this process owns that are running on a managed 283 | * core, from the client's perspective. 284 | */ 285 | uint32_t 286 | CoreArbiterClient::getNumOwnedCores() { 287 | return numOwnedCores.load(); 288 | } 289 | 290 | /** 291 | * Returns the number of threads belonging to this process that are currently 292 | * blocked waiting on a core, from the server's perspective. 293 | */ 294 | uint32_t 295 | CoreArbiterClient::getNumBlockedThreadsFromServer() { 296 | if (serverSocket < 0) { 297 | createNewServerConnection(); 298 | } 299 | 300 | return processStats->numBlockedThreads.load(); 301 | } 302 | 303 | /** 304 | * Returns the number of threads belonging to this process that are currently 305 | * blocked waiting on a core, from the client's perspective. 306 | */ 307 | uint32_t 308 | CoreArbiterClient::getNumBlockedThreads() { 309 | return numBlockedThreads.load(); 310 | } 311 | 312 | /** 313 | * Returns the number of available cores under the server's control that do not 314 | * currently have a thread running exclusively. 315 | */ 316 | size_t 317 | CoreArbiterClient::getNumUnoccupiedCores() { 318 | if (serverSocket < 0) { 319 | createNewServerConnection(); 320 | } 321 | 322 | return globalStats->numUnoccupiedCores; 323 | } 324 | 325 | /** 326 | * Returns the number of processes currently connected to the server. 327 | */ 328 | uint32_t 329 | CoreArbiterClient::getNumProcessesOnServer() { 330 | if (serverSocket < 0) { 331 | createNewServerConnection(); 332 | } 333 | 334 | return globalStats->numProcesses; 335 | } 336 | 337 | // -- private methods 338 | 339 | /** 340 | * Opens a new connection with the server for this thread. If this is the first 341 | * time this process has communicated with the server, it will also set up the 342 | * necessary per-process state. 343 | * 344 | * Throws a ClientException on error. 345 | */ 346 | void 347 | CoreArbiterClient::createNewServerConnection() { 348 | Lock lock(mutex); 349 | if (serverSocket != -1) { 350 | LOG(WARNING, "This thread already has a connection to the server."); 351 | return; 352 | } 353 | 354 | if (testingSkipConnectionSetup) { 355 | LOG(DEBUG, "Skipping connection setup"); 356 | serverSocket = 999; // To tell the test that this method was called 357 | return; 358 | } 359 | 360 | // Set up a socket 361 | serverSocket = sys->socket(AF_UNIX, SOCK_STREAM, 0); 362 | if (serverSocket < 0) { 363 | std::string err = 364 | "Error creating socket: " + std::string(strerror(errno)); 365 | LOG(ERROR, "%s", err.c_str()); 366 | throw ClientException(err); 367 | } 368 | 369 | struct sockaddr_un remote; 370 | memset(&remote, 0, sizeof(remote)); 371 | remote.sun_family = AF_UNIX; 372 | strncpy(remote.sun_path, serverSocketPath.c_str(), 373 | sizeof(remote.sun_path) - 1); 374 | if (sys->connect(serverSocket, (struct sockaddr*)&remote, sizeof(remote)) < 375 | 0) { 376 | std::string err = "Error connecting: " + std::string(strerror(errno)); 377 | LOG(ERROR, "%s", err.c_str()); 378 | serverSocket = -1; // Set serverSocket back to -1 if connection failed 379 | throw ClientException(err); 380 | } 381 | 382 | // Tell the server our process ID 383 | pid_t processId = sys->getpid(); 384 | sendData(serverSocket, &processId, sizeof(pid_t), 385 | "Error sending process ID"); 386 | 387 | // Tell the server our thread ID 388 | pid_t threadId = sys->gettid(); 389 | sendData(serverSocket, &threadId, sizeof(pid_t), "Error sending thread ID"); 390 | 391 | if (!processStats) { 392 | // This is the first time this process is registering so we need to 393 | // set up the shared memory pages 394 | globalSharedMemFd = 395 | openSharedMemory(reinterpret_cast(&globalStats)); 396 | processSharedMemFd = 397 | openSharedMemory(reinterpret_cast(&processStats)); 398 | } 399 | 400 | LOG(NOTICE, "Successfully registered process %d, thread %d with server.", 401 | processId, threadId); 402 | } 403 | 404 | /** 405 | * Opens a shared memory page at a path provided by the server and sets the 406 | * provided pointer to point to the mmapped data. This should be called after 407 | * the server has been informed that it has a new process connecting. 408 | * 409 | * \param bufPtr 410 | * Double pointer to the location of the shared memory structure 411 | * \return 412 | * The fild descriptor of the opened shared memory file 413 | */ 414 | int 415 | CoreArbiterClient::openSharedMemory(void** bufPtr) { 416 | // Read the shared memory path length from the server 417 | size_t pathLen; 418 | readData(serverSocket, &pathLen, sizeof(size_t), 419 | "Error receiving shared memory path length"); 420 | 421 | // Read the shared memory path from the server 422 | char sharedMemPath[pathLen]; 423 | readData(serverSocket, sharedMemPath, pathLen, 424 | "Error receiving shared memory path"); 425 | 426 | // Open the shared memory 427 | int fd = sys->open(sharedMemPath, O_RDONLY); 428 | if (fd < 0) { 429 | std::string err = "Opening shared memory at path " + 430 | std::string(sharedMemPath) + " failed" + 431 | std::string(strerror(errno)); 432 | LOG(ERROR, "%s", err.c_str()); 433 | throw ClientException(err); 434 | } 435 | 436 | *bufPtr = sys->mmap(NULL, getpagesize(), PROT_READ, MAP_SHARED, fd, 0); 437 | if (*bufPtr == MAP_FAILED) { 438 | std::string err = "mmap failed: " + std::string(strerror(errno)); 439 | LOG(ERROR, "%s", err.c_str()); 440 | throw ClientException(err); 441 | } 442 | 443 | return fd; 444 | } 445 | 446 | /** 447 | * Attempts to read numBytes from the provided socket connection into buf. If 448 | * the read fails or does not read the expected amount of data, a 449 | * ClientException is thrown with the provided error message. 450 | * 451 | * \param socket 452 | * The socket connection to read from 453 | * \param buf 454 | * The buffer to write data to 455 | * \param numBytes 456 | * The number of bytes to read 457 | * \param err 458 | * An error string for if the read fails 459 | */ 460 | void 461 | CoreArbiterClient::readData(int socket, void* buf, size_t numBytes, 462 | std::string err) { 463 | ssize_t readBytes = sys->recv(socket, buf, numBytes, 0); 464 | if (readBytes < 0) { 465 | std::string fullErrStr = err + ": " + std::string(strerror(errno)); 466 | LOG(ERROR, "%s", fullErrStr.c_str()); 467 | throw ClientException(fullErrStr); 468 | } else if ((size_t)readBytes < numBytes) { 469 | std::string fullErrStr = err + " TID=" + std::to_string(sys->gettid()) + 470 | ": Expected " + std::to_string(numBytes) + 471 | " bytes but received " + 472 | std::to_string(readBytes); 473 | LOG(ERROR, "%s", fullErrStr.c_str()); 474 | throw ClientException(fullErrStr); 475 | } 476 | } 477 | 478 | /** 479 | * Attempts to send numBytes data of the provided buffer to the provided socket. 480 | * If the send fails, a ClientException is thrown with the provided error 481 | * message. 482 | * 483 | * \param socket 484 | * The socket connection to write to 485 | * \param buf 486 | * The buffer to read data from 487 | * \param numBytes 488 | * The number of bytes to write 489 | * \param err 490 | * An error string for if the send fails 491 | */ 492 | void 493 | CoreArbiterClient::sendData(int socket, void* buf, size_t numBytes, 494 | std::string err) { 495 | if (sys->send(socket, buf, numBytes, 0) < 0) { 496 | std::string fullErrStr = err + ": " + std::string(strerror(errno)); 497 | LOG(ERROR, "%s", fullErrStr.c_str()); 498 | throw ClientException(err); 499 | } 500 | } 501 | 502 | } // namespace CoreArbiter 503 | -------------------------------------------------------------------------------- /src/CoreArbiterClient.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017 Stanford University 2 | * 3 | * Permission to use, copy, modify, and distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #ifndef CORE_ARBITER_CLIENT_H_ 17 | #define CORE_ARBITER_CLIENT_H_ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "CoreArbiterCommon.h" 28 | #include "Syscall.h" 29 | 30 | namespace CoreArbiter { 31 | 32 | /** 33 | * This class provides an interface for running threads on managed cores. We say 34 | * that a thread is on a managed core if it can only run on that core and is the 35 | * only thread that will do so. (The term "managed" comes from the fact that 36 | * this invariant is enforced by the CoreArbiterServer.) 37 | * 38 | * This class is a singleton because applications are expected to manage their 39 | * threads in a coordinated fashion. The user only interacts with the 40 | * CoreArbiterClient, but this class is closely tied to the CoreArbiterServer, 41 | * which is a separate process expected to be running on the same machine. 42 | */ 43 | class CoreArbiterClient { 44 | public: 45 | // Singleton methods 46 | static CoreArbiterClient* getInstance( 47 | std::string serverSocketPath = "/tmp/CoreArbiter/socket") { 48 | static CoreArbiterClient instance(serverSocketPath); 49 | return &instance; 50 | } 51 | CoreArbiterClient(CoreArbiterClient const&) = delete; 52 | void operator=(CoreArbiterClient const&) = delete; 53 | 54 | ~CoreArbiterClient(); 55 | 56 | virtual void setRequestedCores(std::vector numCores); 57 | virtual bool mustReleaseCore(); 58 | virtual bool threadPreempted(); 59 | virtual int blockUntilCoreAvailable(); 60 | virtual uint32_t getNumOwnedCores(); 61 | virtual void unregisterThread(); 62 | virtual int getCoreId(); 63 | 64 | // Meant for testing, not general use 65 | uint32_t getNumOwnedCoresFromServer(); 66 | uint32_t getNumBlockedThreadsFromServer(); 67 | uint32_t getNumBlockedThreads(); 68 | size_t getNumUnoccupiedCores(); 69 | uint32_t getNumProcessesOnServer(); 70 | virtual void reset() {} 71 | 72 | class ClientException : public std::runtime_error { 73 | public: 74 | explicit ClientException(std::string err) : runtime_error(err) {} 75 | }; 76 | 77 | protected: 78 | // Constructor is protected because CoreArbiterClient is a singleton 79 | explicit CoreArbiterClient(std::string serverSocketPath); 80 | 81 | private: 82 | void createNewServerConnection(); 83 | int openSharedMemory(void** bufPtr); 84 | void registerThread(); 85 | void readData(int socket, void* buf, size_t numBytes, std::string err); 86 | void sendData(int socket, void* buf, size_t numBytes, std::string err); 87 | 88 | typedef std::unique_lock Lock; 89 | 90 | // Used to guard data shared across threads, such as processStats. 91 | std::mutex mutex; 92 | 93 | // Information about this processes in shared memory. The server uses this 94 | // struct to communicate with the client about whether it should release a 95 | // core and whether it has a thread preempted. 96 | struct ProcessStats* processStats; 97 | 98 | // Information in shared memory about all processes connected to the same 99 | // server as this client. This is useful primarily for debugging and 100 | // benchmarking. 101 | struct GlobalStats* globalStats; 102 | 103 | // The number of cores that this processes currently owns, i.e. the number 104 | // of threads that it has running on managed cores. This value is also in 105 | // shared memory, but it's useful to have a local copy to know the current 106 | // state of the system from the client's perspective (since socket 107 | // communication lags behind shared memory). 108 | std::atomic numOwnedCores; 109 | 110 | // The number of threads this process currently has blocked waiting to be 111 | // woken up by the server. This value is also in shared memory, but it's 112 | // useful to have a local copy to know the current state of the system from 113 | // the client's perspective (since socket communication lags behind shared 114 | // memory). 115 | std::atomic numBlockedThreads; 116 | 117 | // The path to the socket that the CoreArbiterServer is listening on. 118 | std::string serverSocketPath; 119 | 120 | // The file descriptor whose file contains process-specific information. 121 | // This is mmapped for fast access. 122 | int processSharedMemFd; 123 | 124 | // The file descriptor whose file contains global information aboub all 125 | // clients connected to the server. This is mmapped for fast access. 126 | int globalSharedMemFd; 127 | 128 | // The socket file descriptor used to communicate with the server. Every 129 | // thread has its own socket connection to the server. 130 | static thread_local int serverSocket; 131 | 132 | // The ID of the core that this thread is running on. A value of -1 133 | // indicates that the server has not assigned a core to this thread. Every 134 | // thread has its own coreId. This ID is NOT accurate for threads that have 135 | // been preempted from their managed core. 136 | static thread_local int coreId; 137 | 138 | // Used for all syscalls for easier unit testing. 139 | static Syscall* sys; 140 | 141 | // Useful for unit testing. 142 | static bool testingSkipConnectionSetup; 143 | }; 144 | 145 | } // namespace CoreArbiter 146 | 147 | #endif // CORE_ARBITER_CLIENT_H_ 148 | -------------------------------------------------------------------------------- /src/CoreArbiterClientMain.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017 Stanford University 2 | * 3 | * Permission to use, copy, modify, and distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #include "CoreArbiterClient.h" 21 | #include "Logger.h" 22 | 23 | using CoreArbiter::CoreArbiterClient; 24 | using CoreArbiter::Logger; 25 | 26 | #define NUM_TRIALS 100 27 | 28 | /** 29 | * This thread will get unblocked when a core is allocated, and will block 30 | * itself again when the number of cores is decreased. 31 | */ 32 | void 33 | coreExec(CoreArbiterClient* client) { 34 | client->setRequestedCores({1, 0, 0, 0, 0, 0, 0, 0}); 35 | client->blockUntilCoreAvailable(); 36 | client->setRequestedCores({0, 0, 0, 0, 0, 0, 0, 0}); 37 | while (!client->mustReleaseCore()) 38 | ; 39 | client->unregisterThread(); 40 | } 41 | 42 | int 43 | main() { 44 | Logger::setLogLevel(CoreArbiter::DEBUG); 45 | CoreArbiterClient* client = CoreArbiterClient::getInstance(); 46 | std::thread coreThread(coreExec, std::ref(client)); 47 | 48 | coreThread.join(); 49 | printf("There are %lu cores available\n", client->getNumUnoccupiedCores()); 50 | } 51 | -------------------------------------------------------------------------------- /src/CoreArbiterClientTest.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2018 Stanford University 2 | * 3 | * Permission to use, copy, modify, and distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #define private public 17 | #define protected public 18 | 19 | #include "ArbiterClientShim.h" 20 | #include "CoreArbiterClient.h" 21 | #include "Logger.h" 22 | #include "MockSyscall.h" 23 | 24 | #undef private 25 | #undef protected 26 | #include "gtest/gtest.h" 27 | 28 | namespace CoreArbiter { 29 | 30 | class CoreArbiterClientTest : public ::testing::Test { 31 | public: 32 | MockSyscall* sys; 33 | std::string socketPath; 34 | std::string memPath; 35 | int clientSocket; 36 | int serverSocket; 37 | ProcessStats processStats; 38 | GlobalStats globalStats; 39 | 40 | CoreArbiterClient client; 41 | Arachne::ArbiterClientShim shim_client; 42 | 43 | CoreArbiterClientTest() 44 | : socketPath("/tmp/CoreArbiter/testsocket"), 45 | memPath("/tmp/CoreArbiter/testsocket"), 46 | processStats(), 47 | globalStats(), 48 | client("") { 49 | Logger::setLogLevel(ERROR); 50 | 51 | sys = new MockSyscall(); 52 | CoreArbiterClient::sys = sys; 53 | 54 | int fd[2]; 55 | socketpair(AF_UNIX, SOCK_STREAM, 0, fd); 56 | clientSocket = fd[0]; 57 | serverSocket = fd[1]; 58 | } 59 | 60 | ~CoreArbiterClientTest() { delete sys; } 61 | 62 | void connectClient() { 63 | client.serverSocket = clientSocket; 64 | client.processStats = &processStats; 65 | client.globalStats = &globalStats; 66 | } 67 | 68 | void disconnectClient() { 69 | client.serverSocket = -1; 70 | client.processStats = NULL; 71 | client.globalStats = NULL; 72 | } 73 | }; 74 | 75 | TEST_F(CoreArbiterClientTest, setRequestedCores_invalidRequest) { 76 | CoreArbiterClient::testingSkipConnectionSetup = true; 77 | disconnectClient(); 78 | 79 | // Core request vector too small 80 | ASSERT_THROW(client.setRequestedCores({0}), 81 | CoreArbiterClient::ClientException); 82 | 83 | // Core request vector too large 84 | ASSERT_THROW(client.setRequestedCores({0, 0, 0, 0, 0, 0, 0, 0, 0}), 85 | CoreArbiterClient::ClientException); 86 | } 87 | 88 | TEST_F(CoreArbiterClientTest, setRequestedCores_establishConnection) { 89 | CoreArbiterClient::testingSkipConnectionSetup = true; 90 | disconnectClient(); 91 | 92 | ASSERT_EQ(client.serverSocket, -1); 93 | // This isn't going to work because the client's socket is set to an 94 | // invalid file descriptor for testing 95 | ASSERT_THROW(client.setRequestedCores({0, 0, 0, 0, 0, 0, 0, 0}), 96 | CoreArbiterClient::ClientException); 97 | ASSERT_EQ(client.serverSocket, 999); 98 | } 99 | 100 | TEST_F(CoreArbiterClientTest, setRequestedCores) { 101 | connectClient(); 102 | client.setRequestedCores({0, 1, 2, 3, 4, 5, 6, 7}); 103 | client.serverSocket = -1; 104 | 105 | uint8_t msgType; 106 | recv(serverSocket, &msgType, sizeof(msgType), 0); 107 | ASSERT_EQ(msgType, CORE_REQUEST); 108 | 109 | uint32_t requestArr[NUM_PRIORITIES]; 110 | recv(serverSocket, requestArr, sizeof(requestArr), 0); 111 | 112 | for (uint32_t i = 0; i < NUM_PRIORITIES; i++) { 113 | ASSERT_EQ(requestArr[i], i); 114 | } 115 | } 116 | 117 | TEST_F(CoreArbiterClientTest, mustReleaseCore) { 118 | connectClient(); 119 | ASSERT_FALSE(client.mustReleaseCore()); 120 | int coreId = 0; 121 | 122 | client.coreId = coreId; 123 | processStats.threadCommunicationBlocks[coreId].coreReleaseRequested = true; 124 | ASSERT_TRUE(client.mustReleaseCore()); 125 | 126 | // Simulate a blockUntilCoreAvailable() call 127 | processStats.threadCommunicationBlocks[coreId].coreReleaseRequested = false; 128 | 129 | ASSERT_FALSE(client.mustReleaseCore()); 130 | } 131 | 132 | TEST_F(CoreArbiterClientTest, blockUntilCoreAvailable_establishConnection) { 133 | CoreArbiterClient::testingSkipConnectionSetup = true; 134 | disconnectClient(); 135 | 136 | ASSERT_EQ(client.serverSocket, -1); 137 | // This isn't going to work because the client's socket is set to an 138 | // invalid file descriptor for testing 139 | ASSERT_THROW(client.blockUntilCoreAvailable(), 140 | CoreArbiterClient::ClientException); 141 | ASSERT_EQ(client.serverSocket, 999); 142 | } 143 | 144 | TEST_F(CoreArbiterClientTest, blockUntilCoreAvailable_alreadyExclusive) { 145 | connectClient(); 146 | client.coreId = 1; 147 | client.processStats->numOwnedCores = 1; 148 | 149 | // Thread should not be allowed to block 150 | EXPECT_EQ(client.blockUntilCoreAvailable(), 1); 151 | EXPECT_EQ(client.processStats->numOwnedCores, 1u); 152 | 153 | // This time thread should block because it owes the server a core 154 | processStats.threadCommunicationBlocks[client.coreId].coreReleaseRequested = 155 | true; 156 | int coreId = 2; 157 | send(serverSocket, &coreId, sizeof(int), 0); 158 | EXPECT_EQ(client.blockUntilCoreAvailable(), 2); 159 | EXPECT_EQ(client.processStats->numOwnedCores, 1u); 160 | 161 | // Same test, but this time with a pending release 162 | send(serverSocket, &coreId, sizeof(int), 0); 163 | EXPECT_EQ(client.blockUntilCoreAvailable(), 2); 164 | EXPECT_EQ(client.processStats->numOwnedCores, 1u); 165 | 166 | uint8_t blockMsg; 167 | recv(serverSocket, &blockMsg, sizeof(uint8_t), 0); 168 | EXPECT_EQ(blockMsg, THREAD_BLOCK); 169 | } 170 | 171 | TEST_F(CoreArbiterClientTest, getNumOwnedCores) { 172 | client.numOwnedCores = 99; 173 | EXPECT_EQ(client.getNumOwnedCores(), 99u); 174 | } 175 | 176 | TEST_F(CoreArbiterClientTest, setRequestedCores_shim) { 177 | shim_client.setRequestedCores({0, 1, 2, 3, 4, 5, 6, 7}); 178 | ASSERT_EQ(shim_client.currentRequestedCores, (unsigned)28); 179 | } 180 | 181 | TEST_F(CoreArbiterClientTest, mustReleaseCore_shim) { 182 | shim_client.currentCores = 26; 183 | ASSERT_EQ(shim_client.mustReleaseCore(), true); 184 | } 185 | 186 | } // namespace CoreArbiter 187 | -------------------------------------------------------------------------------- /src/CoreArbiterCommon.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017 Stanford University 2 | * 3 | * Permission to use, copy, modify, and distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #ifndef CORE_ARBITER_COMMON_H 17 | #define CORE_ARBITER_COMMON_H 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #define NUM_PRIORITIES 8 25 | #define RELEASE_TIMEOUT_MS 10 26 | #define CPUSET_UPDATE_TIMEOUT_MS 10 27 | 28 | #define THREAD_BLOCK 1 29 | #define CORE_REQUEST 2 30 | 31 | #define MAX_SUPPORTED_CORES 256 32 | 33 | namespace CoreArbiter { 34 | 35 | /** 36 | * Members of this structure are used by the CoreArbiter to efficiently pass 37 | * information to individual threads of a process. 38 | */ 39 | struct ThreadCommunicationBlock { 40 | // True means that the thread should yield its core. 41 | std::atomic coreReleaseRequested; 42 | }; 43 | /** 44 | * Statistics kept per process. The server creates a file with this information 45 | * which is mmapped into memory by both the server and client. Only the server 46 | * can write to the shared memory. 47 | */ 48 | struct ProcessStats { 49 | // A monotonically increasing count of the number of times the server has 50 | // forceably moved a thread belonging to this process to the unmanaged core 51 | // because it did not release a core when requested to. 52 | std::atomic preemptedCount; 53 | 54 | // A monotonically increasing count of the number of times the server has 55 | // moved a forceably preempted thread back to an exclusive core. 56 | // preemptedCount - unpreemptedCount tells you the number of threads that a 57 | // process currently has running on the unmanaged core that were moved from 58 | // exclusive cores. 59 | std::atomic unpreemptedCount; 60 | 61 | // The number of threads that a processes currently has blocked waiting for 62 | // the server to assign them a core. 63 | std::atomic numBlockedThreads; 64 | 65 | // The number of cores that this process currently has threads running 66 | // exclusively on. 67 | std::atomic numOwnedCores; 68 | 69 | // This array is indexed by physical core ID, which each thread receives as 70 | // the return value of blockUntilCoreAvailable. 71 | ThreadCommunicationBlock threadCommunicationBlocks[MAX_SUPPORTED_CORES]; 72 | 73 | ProcessStats() 74 | : preemptedCount(0), 75 | unpreemptedCount(0), 76 | numBlockedThreads(0), 77 | numOwnedCores(0) { 78 | memset(threadCommunicationBlocks, 0, sizeof(threadCommunicationBlocks)); 79 | } 80 | }; 81 | 82 | /** 83 | * Statistics kept accross all processes. The server creates a file with this 84 | * information which is mmapped into memory by both the server and client. Only 85 | * the server can write to the shared memory. 86 | */ 87 | struct GlobalStats { 88 | // The number of cores that a CoreArbiterServer controls that do not 89 | // currently have a thread running on them. 90 | std::atomic numUnoccupiedCores; 91 | 92 | // The total number of processes currently connected to a CoreArbiterServer 93 | std::atomic numProcesses; 94 | 95 | GlobalStats() : numUnoccupiedCores(0), numProcesses(0) {} 96 | }; 97 | 98 | } // namespace CoreArbiter 99 | 100 | #endif // CORE_ARBITER_COMMON_H 101 | -------------------------------------------------------------------------------- /src/CoreArbiterRampDownTest.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2018 Stanford University 2 | * 3 | * Permission to use, copy, modify, and distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #include "CoreArbiterClient.h" 21 | #include "Logger.h" 22 | #include "PerfUtils/Cycles.h" 23 | #include "PerfUtils/Stats.h" 24 | #include "PerfUtils/TimeTrace.h" 25 | #include "PerfUtils/Util.h" 26 | 27 | /** 28 | * This benchmark will rapidly increase and decrease the number of cores 29 | * requested, to stress the core arbiter's allocation and deallocation 30 | * mechanism. 31 | */ 32 | 33 | // Uncomment the following line to make this benchmark pause for 2 seconds 34 | // between allocations so that we observe the order of allocation and 35 | // de-allocation. 36 | // #define PAUSE_AT_ALLOCATION 1 37 | 38 | using CoreArbiter::CoreArbiterClient; 39 | using PerfUtils::Cycles; 40 | using PerfUtils::TimeTrace; 41 | 42 | #define NUM_TRIALS 100 43 | 44 | std::atomic end(false); 45 | std::atomic numActiveCores; 46 | 47 | /** 48 | * This thread will block and unblock on the Core Arbiter's command. 49 | */ 50 | void 51 | coreExec(CoreArbiterClient* client) { 52 | while (!end) { 53 | client->blockUntilCoreAvailable(); 54 | numActiveCores++; 55 | while (!client->mustReleaseCore()) 56 | ; 57 | numActiveCores--; 58 | } 59 | } 60 | 61 | // Helper function for tests with timing dependencies, so that we wait for a 62 | // finite amount of time in the case of a bug causing an infinite loop. 63 | static void 64 | limitedTimeWait(std::function condition, int numIterations = 1000) { 65 | for (int i = 0; i < numIterations; i++) { 66 | if (condition()) { 67 | return; 68 | } 69 | usleep(1000); 70 | } 71 | fprintf(stderr, "Failed to wait for condition to be true.\n"); 72 | } 73 | 74 | /** 75 | * This thread will request a large number of cores, and then gradually request 76 | * a smaller number of cores, verifying that we eventually get 77 | * mustReleaseCore() called on us. 78 | */ 79 | int 80 | main(int argc, const char** argv) { 81 | const uint32_t MAX_CORES = std::thread::hardware_concurrency() - 1; 82 | CoreArbiterClient* client = CoreArbiterClient::getInstance(); 83 | 84 | // Start up several threads to actually ramp up and down 85 | for (uint32_t i = 0; i < MAX_CORES; i++) 86 | (new std::thread(coreExec, std::ref(client)))->detach(); 87 | 88 | std::vector coreRequest = {MAX_CORES, 0, 0, 0, 0, 0, 0, 0}; 89 | client->setRequestedCores(coreRequest); 90 | // Wait until we actually have that many cores 91 | while (numActiveCores.load() != coreRequest[0]) 92 | ; 93 | 94 | // Then, verify that we can step down, with a limited time wait. 95 | coreRequest[0] = 0; 96 | client->setRequestedCores(coreRequest); 97 | limitedTimeWait([]() -> bool { return numActiveCores == 0; }); 98 | 99 | // Go back up and exit. 100 | coreRequest[0] = MAX_CORES; 101 | client->setRequestedCores(coreRequest); 102 | end.store(true); 103 | } 104 | -------------------------------------------------------------------------------- /src/CoreArbiterRequestTest.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2018 Stanford University 2 | * 3 | * Permission to use, copy, modify, and distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #include "CoreArbiterClient.h" 21 | #include "Logger.h" 22 | #include "PerfUtils/Cycles.h" 23 | #include "PerfUtils/Stats.h" 24 | #include "PerfUtils/TimeTrace.h" 25 | #include "PerfUtils/Util.h" 26 | 27 | /** 28 | * This benchmark will rapidly increase and decrease the number of cores 29 | * requested, to stress the core arbiter's allocation and deallocation 30 | * mechanism. 31 | */ 32 | 33 | // Uncomment the following line to make this benchmark pause for 2 seconds 34 | // between allocations so that we observe the order of allocation and 35 | // de-allocation. 36 | // #define PAUSE_AT_ALLOCATION 1 37 | 38 | using CoreArbiter::CoreArbiterClient; 39 | using PerfUtils::Cycles; 40 | using PerfUtils::TimeTrace; 41 | 42 | #define NUM_TRIALS 100 43 | 44 | std::atomic end(false); 45 | 46 | /** 47 | * This thread will block and unblock on the Core Arbiter's command. 48 | */ 49 | void 50 | coreExec(CoreArbiterClient* client) { 51 | while (!end) { 52 | client->blockUntilCoreAvailable(); 53 | while (!client->mustReleaseCore()) 54 | ; 55 | } 56 | } 57 | 58 | /** 59 | * This thread will request an increasing number of cores and then a decreasing 60 | * number of cores. 61 | */ 62 | int 63 | main(int argc, const char** argv) { 64 | const int MAX_CORES = std::thread::hardware_concurrency() - 1; 65 | CoreArbiterClient* client = CoreArbiterClient::getInstance(); 66 | 67 | // Start up several threads to actually ramp up and down 68 | for (int i = 0; i < MAX_CORES; i++) 69 | (new std::thread(coreExec, std::ref(client)))->detach(); 70 | 71 | std::vector coreRequest = {0, 0, 0, 0, 0, 0, 0, 0}; 72 | 73 | for (int i = 0; i < NUM_TRIALS; i++) { 74 | // First go up and then go down. 75 | int j; 76 | for (j = 1; j < MAX_CORES; j++) { 77 | coreRequest[0] = j; 78 | client->setRequestedCores(coreRequest); 79 | #if PAUSE_AT_ALLOCATION 80 | sleep(2); 81 | #endif 82 | } 83 | for (; j > 0; j--) { 84 | coreRequest[0] = j; 85 | client->setRequestedCores(coreRequest); 86 | #if PAUSE_AT_ALLOCATION 87 | sleep(2); 88 | #endif 89 | } 90 | } 91 | coreRequest[0] = MAX_CORES; 92 | client->setRequestedCores(coreRequest); 93 | end.store(true); 94 | } 95 | -------------------------------------------------------------------------------- /src/CoreArbiterServer.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017 Stanford University 2 | * 3 | * Permission to use, copy, modify, and distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #ifndef CORE_ARBITER_SERVER_H_ 17 | #define CORE_ARBITER_SERVER_H_ 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "CoreArbiterCommon.h" 32 | #include "Logger.h" 33 | #include "PerfUtils/Cycles.h" 34 | #include "Syscall.h" 35 | 36 | #define MAX_EPOLL_EVENTS 1000 37 | 38 | using PerfUtils::Cycles; 39 | 40 | namespace CoreArbiter { 41 | 42 | /** 43 | * This class implements a service that arbitrates core allocations amongst 44 | * application. It handles a set of "managed" cores, on which only a single 45 | * thread can run, and "unmanaged" cores, which obey the kernel's thread 46 | * scheduling as usual. It is meant to be run as a daemon on a system. A typical 47 | * use would construct a server object and call startArbitration(), which does 48 | * not exit. There should be only one instance of the CoreArbiterServer on a 49 | * machine at any given time. 50 | * 51 | * This class is tightly connected to CoreArbiterClient. Users should not 52 | * attempt to connect to the server directly, but rather go through the client's 53 | * API. 54 | */ 55 | class CoreArbiterServer { 56 | public: 57 | CoreArbiterServer(std::string socketPath, std::string sharedMemPathPrefix, 58 | std::vector managedCores = {}, 59 | bool arbitrateImmediately = true); 60 | ~CoreArbiterServer(); 61 | void startArbitration(); 62 | void endArbitration(); 63 | 64 | // Point at the most recently constructed instance of the 65 | // CoreArbiterServer. 66 | static CoreArbiterServer* volatile mostRecentInstance; 67 | 68 | private: 69 | struct ThreadInfo; 70 | struct ProcessInfo; 71 | struct CoreInfo; 72 | 73 | /** 74 | * Used to keep track of all the information for a core. There is a separate 75 | * CoreInfo instance for every core that the server has control over (both 76 | * managed and unmanaged). These structs are constructed when the server 77 | * starts up and exist for the server's entire lifetime. 78 | */ 79 | struct CoreInfo { 80 | // The ID of this core. This ID matches what would be returned by a 81 | // thread on this core that ran sched_getcpu(). 82 | int id; 83 | 84 | // A pointer to the thread running on this core if the core is managed. 85 | // NULL otherwise. 86 | struct ThreadInfo* managedThread; 87 | 88 | // The name of this core's managed cpuset tasks file. 89 | std::string cpusetFilename; 90 | 91 | // A stream pointing to the tasks file of this core's managed cpuset. 92 | std::ofstream cpusetFile; 93 | 94 | // The last time (in cycles) that this core had a thread removed from 95 | // it. If there is no thread running on this core, this value tells us 96 | // how long the core has been unoccupied. 97 | uint64_t threadRemovalTime; 98 | 99 | CoreInfo() : managedThread(NULL) {} 100 | 101 | CoreInfo(int id, std::string managedTasksPath) 102 | : id(id), 103 | managedThread(NULL), 104 | cpusetFilename(managedTasksPath), 105 | threadRemovalTime(0) { 106 | if (!testingSkipCpusetAllocation) { 107 | cpusetFile.open(cpusetFilename); 108 | if (!cpusetFile.is_open()) { 109 | LOG(ERROR, "Unable to open %s", cpusetFilename.c_str()); 110 | exit(-1); 111 | } 112 | } 113 | } 114 | }; 115 | 116 | /** 117 | * Used by ThreadInfo to keep track of a thread's state. 118 | */ 119 | enum ThreadState { 120 | // Running on a managed core 121 | RUNNING_MANAGED, 122 | 123 | // Voluntarily running on the unmanaged core (this only happens before 124 | // the first call to blockUntilCoreAvailable()) 125 | RUNNING_UNMANAGED, 126 | 127 | // Running on the unmanaged core because it was forceably preempted from 128 | // its managed core 129 | RUNNING_PREEMPTED, 130 | 131 | // Not running, waiting to be put on core 132 | BLOCKED 133 | }; 134 | 135 | /** 136 | * Keeps track of all the information for a thread registered with this 137 | * server. A ThreadInfo instance exists from the time that a new thread 138 | * first connects with a server until that connection closes. 139 | */ 140 | struct ThreadInfo { 141 | // The ID of this thread (self-reported when the thread first 142 | // establishes a connection). All threads within a process are expected 143 | // to have unique IDs. 144 | pid_t id; 145 | 146 | // A pointer to the process that this thread belongs to. 147 | struct ProcessInfo* process; 148 | 149 | // The file descriptor for the socket used to communicate with this 150 | // thread. 151 | int socket; 152 | 153 | // A pointer to the managed core this thread is running on. NULL if this 154 | // thread is not running on a managed core. 155 | struct CoreInfo* core; 156 | 157 | // A pointer to the managed core this thread was running before it was 158 | // preempted. If this thread becomes unpreempted before it blocks, it 159 | // must return to this core. 160 | struct CoreInfo* corePreemptedFrom; 161 | 162 | // The current state of this thread. When a thread first registers it 163 | // is assumed to be RUNNING_UNMANAGED. 164 | ThreadState state; 165 | 166 | ThreadInfo() {} 167 | 168 | ThreadInfo(pid_t threadId, struct ProcessInfo* process, int socket) 169 | : id(threadId), 170 | process(process), 171 | socket(socket), 172 | core(NULL), 173 | corePreemptedFrom(NULL), 174 | state(RUNNING_UNMANAGED) {} 175 | }; 176 | 177 | /** 178 | * Keeps track of all the information for a process, including which threads 179 | * belong to this process. ProcessInfo instances are generated as needed, 180 | * when a thread registers with a proces that we have not seen before. A 181 | * process is not deleted from memory until all of its threads' connections 182 | * have closed. 183 | */ 184 | struct ProcessInfo { 185 | // The ID of this process (self-reported when a thread first establishes 186 | // a connection). All processes on this machine are expected to have 187 | // unique IDs. 188 | pid_t id; 189 | 190 | // The file descriptor that is mmapped into memory for communication 191 | // between the process and server (see the stats struct below). 192 | int sharedMemFd; 193 | 194 | // A pointer to shared memory that is used to communicate information 195 | // to this process. 196 | struct ProcessStats* stats; 197 | 198 | // The set of cores that threads belonging to this process have been 199 | // timeout-preempted from. We must ensure that no threads from this 200 | // process are scheduled onto this core until the preempted thread 201 | // blocks and can be assigned a new core. 202 | std::unordered_set coresPreemptedFrom; 203 | 204 | // How many cores this process desires at each priority level. Smaller 205 | // indexes mean higher priority. 206 | std::vector desiredCorePriorities; 207 | 208 | // A map of ThreadState to the threads this process owns in that state. 209 | std::unordered_map, 210 | std::hash> 211 | threadStateToSet; 212 | 213 | ProcessInfo() : desiredCorePriorities(NUM_PRIORITIES) {} 214 | 215 | ProcessInfo(pid_t id, int sharedMemFd, struct ProcessStats* stats) 216 | : id(id), 217 | sharedMemFd(sharedMemFd), 218 | stats(stats), 219 | desiredCorePriorities(NUM_PRIORITIES) {} 220 | }; 221 | 222 | /** 223 | * Data structure that stores a process and the core it was asked to 224 | * relinquish. This prevents the server from preempting a thread that has 225 | * already yielded. 226 | */ 227 | struct TimerInfo { 228 | pid_t processId; 229 | CoreInfo* coreInfo; 230 | }; 231 | 232 | bool handleEvents(); 233 | void acceptConnection(int listenSocket); 234 | void threadBlocking(int socket); 235 | void coresRequested(int socket); 236 | void timeoutThreadPreemption(int timerFd); 237 | void cleanupConnection(int socket); 238 | CoreInfo* findGoodCoreForProcess(ProcessInfo* process, 239 | std::deque& candidates); 240 | void wakeupThread(ThreadInfo* thread, CoreInfo* core); 241 | void distributeCores(); 242 | void requestCoreRelease(struct CoreInfo* core); 243 | 244 | bool readData(int socket, void* buf, size_t numBytes, std::string err); 245 | bool sendData(int socket, void* buf, size_t numBytes, std::string err); 246 | 247 | void createCpuset(std::string dirName, std::string cores, std::string mems); 248 | void moveProcsToCpuset(std::string fromPath, std::string toPath); 249 | void removeUnmanagedThreadsFromCore(struct CoreInfo* core); 250 | void removeOldCpusets(std::string arbiterCpusetPath); 251 | bool moveThreadToManagedCore(struct ThreadInfo* thread, 252 | struct CoreInfo* core); 253 | void removeThreadFromManagedCore(struct ThreadInfo* thread, 254 | bool changeCpuset = true); 255 | void updateUnmanagedCpuset(); 256 | void changeThreadState(struct ThreadInfo* thread, ThreadState state); 257 | 258 | void installSignalHandler(); 259 | 260 | // The path to the socket that the server is listening for new connections 261 | // on. 262 | std::string socketPath; 263 | 264 | // The file descriptor for the socket that the server is listening for new 265 | // connections on. 266 | int listenSocket; 267 | 268 | // The prefix that will be used to generate shared memory file paths for 269 | // each process. This can be either a file or directory. 270 | std::string sharedMemPathPrefix; 271 | 272 | // The path to the shared memory file with global server information (see 273 | // the GlobalStats struct below). 274 | std::string globalSharedMemPath; 275 | 276 | // The file descriptor for the shared memory file with global server 277 | // information (see the GlobalStats struct below). 278 | int globalSharedMemFd; 279 | 280 | // The path to the advisory lock file, preventing multi 281 | // coreArbiterServer instances 282 | std::string advisoryLockPath; 283 | 284 | // The file descriptor for the advisory lock file 285 | int advisoryLockFd; 286 | 287 | // Pointer to a struct in shared memory that contains global information 288 | // about the state of all processes connected to this server. 289 | struct GlobalStats* stats; 290 | 291 | // The file descriptor used to block on client requests. 292 | int epollFd; 293 | 294 | // A map of core preemption timers to their related information. 295 | std::unordered_map timerFdToInfo; 296 | 297 | // The amount of time in milliseconds to wait before forceably preempting 298 | // a thread from its managed core to the unmanaged core. 299 | uint64_t preemptionTimeout; 300 | 301 | // Maps thread socket file desriptors to their associated threads. 302 | std::unordered_map threadSocketToInfo; 303 | 304 | // Maps process IDs to their associated processes. 305 | std::unordered_map processIdToInfo; 306 | 307 | // Contains the information about cores that are not currently in the 308 | // unmanaged cpuset. This vector grows with cores from unmanagedCores when 309 | // the arbiter is loaded and shrinks when there are fewer cores being used. 310 | std::vector managedCores; 311 | 312 | // Contains the information about cores that are currently in the unmanaged 313 | // cpuset. At startup, this vector contains all cores controlled by the 314 | // arbiter. It shrinks as cores are requested and grows when cores are 315 | // unused for an extended period. 316 | std::deque unmanagedCores; 317 | 318 | // The file used to change which cores belong to the unmanaged cpuset. 319 | std::ofstream unmanagedCpusetCpus; 320 | 321 | // The file used to change which threads are running on the unmanaged 322 | // cpuset. 323 | std::ofstream unmanagedCpusetTasks; 324 | 325 | // A comma-delimited string of CPU IDs for cores not under the arbiter's 326 | // control. 327 | std::string alwaysUnmanagedString; 328 | 329 | // The last time (in cycles) that the unmanaged cpuset's set of cores was 330 | // updated. 331 | uint64_t unmanagedCpusetLastUpdate; 332 | 333 | // The minimum amount of time (in milliseconds) to wait before adding an 334 | // unoccupied core to the unmanaged cpuset. Also the minimum amount of time 335 | // to wait before updating the unmanaged cpuset's cores. This timeout is 336 | // necessary to make sure we don't change the unmanaged cpuset too often, as 337 | // doing so will cause the kernel to throw errors. 338 | uint64_t cpusetUpdateTimeout; 339 | 340 | // The set of the threads currently running on cores in managedCores. 341 | std::vector managedThreads; 342 | 343 | // The smallest index in the vector is the highest priority and the first 344 | // entry in the deque is the next process that should receive a core at 345 | // that priority. 346 | std::vector> corePriorityQueues; 347 | 348 | // When this file descriptor is written, the core arbiter will return from 349 | // startArbitration. 350 | volatile int terminationFd; 351 | 352 | // The path to the root cpuset directory. 353 | static std::string cpusetPath; 354 | 355 | // Wrap all system calls for easier testing. 356 | static Syscall* sys; 357 | 358 | // Used for testing to avoid unnecessary setup and code execution. 359 | static bool testingSkipCpusetAllocation; 360 | static bool testingSkipCoreDistribution; 361 | static bool testingSkipSocketCommunication; 362 | static bool testingSkipMemoryDeallocation; 363 | static bool testingDoNotChangeManagedCores; 364 | }; 365 | 366 | } // namespace CoreArbiter 367 | 368 | int ensureParents(const char* path, mode_t mode = S_IRWXU); 369 | 370 | #endif // CORE_ARBITER_SERVER_H_ 371 | -------------------------------------------------------------------------------- /src/CoreArbiterServerMain.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017 Stanford University 2 | * 3 | * Permission to use, copy, modify, and distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #include 17 | #include "CoreArbiterServer.h" 18 | #include "Logger.h" 19 | #include "PerfUtils/Util.h" 20 | 21 | using CoreArbiter::CoreArbiterServer; 22 | using CoreArbiter::Logger; 23 | 24 | std::string socketPath = "/tmp/CoreArbiter/socket"; 25 | std::string sharedMemoryPath = "/tmp/CoreArbiter/sharedmemory"; 26 | std::vector coresUsed = std::vector(); 27 | 28 | const char usage[] = 29 | "Usage: ./coreArbiterServer [-h] [--coresUsed ]" 30 | " [--socketPath ] [--sharedMemoryPath ]" 31 | " [--logLevel DEBUG|NOTICE|WARNING|ERROR|SILENT]"; 32 | 33 | /** 34 | * This function currently supports only long options. 35 | */ 36 | void 37 | parseOptions(int* argcp, const char** argv) { 38 | if (argcp == NULL) 39 | return; 40 | 41 | int argc = *argcp; 42 | 43 | struct OptionSpecifier { 44 | // The string that the user uses after `--`. 45 | const char* optionName; 46 | // The id for the option that is returned when it is recognized. 47 | int id; 48 | // Does the option take an argument? 49 | bool takesArgument; 50 | } optionSpecifiers[] = {{"help", 'h', false}, 51 | {"socketPath", 'p', true}, 52 | {"sharedMemoryPath", 'm', true}, 53 | {"coresUsed", 's', true}, 54 | {"logLevel", 'l', true}}; 55 | const int UNRECOGNIZED = ~0; 56 | 57 | int i = 1; 58 | while (i < argc) { 59 | if (argv[i][0] != '-' || argv[i][1] != '-') { 60 | i++; 61 | continue; 62 | } 63 | const char* optionName = argv[i] + 2; 64 | int optionId = UNRECOGNIZED; 65 | const char* optionArgument = NULL; 66 | 67 | for (size_t k = 0; 68 | k < sizeof(optionSpecifiers) / sizeof(OptionSpecifier); k++) { 69 | const char* candidateName = optionSpecifiers[k].optionName; 70 | bool needsArg = optionSpecifiers[k].takesArgument; 71 | if (strncmp(candidateName, optionName, strlen(candidateName)) == 72 | 0) { 73 | if (needsArg) { 74 | if (i + 1 >= argc) { 75 | LOG(CoreArbiter::ERROR, 76 | "Missing argument to option %s!\n", candidateName); 77 | break; 78 | } 79 | optionArgument = argv[i + 1]; 80 | optionId = optionSpecifiers[k].id; 81 | argc -= 2; 82 | memmove(argv + i, argv + i + 2, (argc - i) * sizeof(char*)); 83 | } else { 84 | optionId = optionSpecifiers[k].id; 85 | argc -= 1; 86 | memmove(argv + i, argv + i + 1, (argc - i) * sizeof(char*)); 87 | } 88 | break; 89 | } 90 | } 91 | switch (optionId) { 92 | case 'h': 93 | puts(usage); 94 | exit(0); 95 | case 'p': 96 | socketPath = optionArgument; 97 | break; 98 | case 'm': 99 | sharedMemoryPath = optionArgument; 100 | break; 101 | case 's': 102 | if (memcmp(optionArgument, "ALL", sizeof("ALL")) == 0) 103 | coresUsed = std::vector(); 104 | else 105 | coresUsed = PerfUtils::Util::parseRanges(optionArgument); 106 | break; 107 | case 'l': 108 | Logger::setLogLevel(optionArgument); 109 | break; 110 | case UNRECOGNIZED: 111 | LOG(CoreArbiter::ERROR, "Unrecognized option %s given.", 112 | optionName); 113 | abort(); 114 | } 115 | } 116 | *argcp = argc; 117 | } 118 | 119 | int 120 | main(int argc, const char** argv) { 121 | Logger::setLogLevel(CoreArbiter::ERROR); 122 | parseOptions(&argc, argv); 123 | printf("socketPath: %s\n", socketPath.c_str()); 124 | printf("sharedMemoryPath: %s\n", sharedMemoryPath.c_str()); 125 | printf("coresUsed: "); 126 | if (coresUsed.empty()) { 127 | printf(" ALL\n"); 128 | } else { 129 | for (size_t i = 0; i < coresUsed.size(); i++) 130 | printf(" %d", coresUsed[i]); 131 | putchar('\n'); 132 | } 133 | fflush(stdout); 134 | 135 | CoreArbiterServer server(socketPath, sharedMemoryPath, coresUsed); 136 | return 0; 137 | } 138 | -------------------------------------------------------------------------------- /src/CoreArbiterServerTest.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017 Stanford University 2 | * 3 | * Permission to use, copy, modify, and distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #include 17 | #define private public 18 | 19 | #include "CoreArbiterServer.h" 20 | #include "Logger.h" 21 | #include "MockSyscall.h" 22 | 23 | #undef private 24 | #include "gtest/gtest.h" 25 | 26 | namespace CoreArbiter { 27 | 28 | class CoreArbiterServerTest : public ::testing::Test { 29 | public: 30 | MockSyscall* sys; 31 | std::string socketPath; 32 | std::string memPath; 33 | int clientSocket; 34 | int serverSocket; 35 | 36 | typedef CoreArbiterServer::ThreadInfo ThreadInfo; 37 | typedef CoreArbiterServer::ProcessInfo ProcessInfo; 38 | typedef CoreArbiterServer::CoreInfo CoreInfo; 39 | typedef CoreArbiterServer::ThreadState ThreadState; 40 | 41 | CoreArbiterServerTest() 42 | : socketPath("/tmp/CoreArbiter/testsocket"), 43 | memPath("/tmp/CoreArbiter/testmem") { 44 | Logger::setLogLevel(ERROR); 45 | 46 | sys = new MockSyscall(); 47 | CoreArbiterServer::sys = sys; 48 | 49 | int fd[2]; 50 | socketpair(AF_UNIX, SOCK_STREAM, 0, fd); 51 | clientSocket = fd[0]; 52 | serverSocket = fd[1]; 53 | } 54 | 55 | ~CoreArbiterServerTest() { 56 | close(clientSocket); 57 | close(serverSocket); 58 | delete sys; 59 | } 60 | 61 | /** 62 | * The process that this method creates needs to be freed by the caller. 63 | */ 64 | ProcessInfo* createProcess(CoreArbiterServer& server, pid_t processId, 65 | ProcessStats* stats) { 66 | ProcessInfo* process = new ProcessInfo(processId, 0, stats); 67 | server.processIdToInfo[processId] = process; 68 | return process; 69 | } 70 | 71 | /** 72 | * The thread that this method creates needs to be freed by the caller. 73 | */ 74 | ThreadInfo* createThread(CoreArbiterServer& server, pid_t threadId, 75 | ProcessInfo* process, int socket, 76 | ThreadState state, CoreInfo* core = NULL) { 77 | ThreadInfo* thread = new ThreadInfo(threadId, process, socket); 78 | thread->state = state; 79 | process->threadStateToSet[state].insert(thread); 80 | server.threadSocketToInfo[socket] = thread; 81 | if (state == CoreArbiterServer::RUNNING_MANAGED) { 82 | server.managedThreads.push_back(thread); 83 | process->stats->numOwnedCores++; 84 | thread->core = core; 85 | core->managedThread = thread; 86 | } else if (state == CoreArbiterServer::RUNNING_PREEMPTED) { 87 | process->stats->preemptedCount++; 88 | } 89 | return thread; 90 | } 91 | 92 | void makeUnmanagedCoresManaged(CoreArbiterServer& server) { 93 | server.managedCores.insert(server.managedCores.end(), 94 | server.unmanagedCores.begin(), 95 | server.unmanagedCores.end()); 96 | server.unmanagedCores.erase(server.unmanagedCores.begin()); 97 | } 98 | }; 99 | 100 | TEST_F(CoreArbiterServerTest, constructor_notRoot) { 101 | sys->callGeteuid = false; 102 | sys->geteuidResult = 1; 103 | ASSERT_DEATH(CoreArbiterServer(socketPath, memPath, {}), 104 | "The core arbiter server must be run as root"); 105 | sys->callGeteuid = true; 106 | } 107 | 108 | TEST_F(CoreArbiterServerTest, constructor_socketError) { 109 | sys->socketErrno = EAFNOSUPPORT; 110 | ASSERT_DEATH(CoreArbiterServer(socketPath, memPath, {}), 111 | "Error creating listen socket:.*"); 112 | sys->socketErrno = 0; 113 | } 114 | 115 | TEST_F(CoreArbiterServerTest, constructor_bindError) { 116 | sys->bindErrno = EINVAL; 117 | std::string expectedError = 118 | "Error binding listen socket " + socketPath + ":.*"; 119 | ASSERT_DEATH(CoreArbiterServer(socketPath, memPath, {}), 120 | expectedError.c_str()); 121 | sys->bindErrno = 0; 122 | } 123 | 124 | TEST_F(CoreArbiterServerTest, constructor_listenError) { 125 | sys->listenErrno = EBADF; 126 | ASSERT_DEATH(CoreArbiterServer(socketPath, memPath, {}), 127 | "Error listening:.*"); 128 | sys->listenErrno = 0; 129 | } 130 | 131 | TEST_F(CoreArbiterServerTest, constructor_chmodError) { 132 | sys->chmodErrno = EACCES; 133 | ASSERT_DEATH(CoreArbiterServer(socketPath, memPath, {}), 134 | "Error on chmod for.*"); 135 | sys->chmodErrno = 0; 136 | } 137 | 138 | TEST_F(CoreArbiterServerTest, constructor_epollCreateError) { 139 | sys->epollCreateErrno = EINVAL; 140 | ASSERT_DEATH(CoreArbiterServer(socketPath, memPath, {}), 141 | "Error on epoll_create:.*"); 142 | sys->epollCreateErrno = 0; 143 | } 144 | 145 | TEST_F(CoreArbiterServerTest, constructor_epollCtlError) { 146 | sys->epollCtlErrno = EBADF; 147 | ASSERT_DEATH(CoreArbiterServer(socketPath, memPath, {}), 148 | "Error adding listenSocket .* to epoll:.*"); 149 | sys->epollCtlErrno = 0; 150 | } 151 | 152 | TEST_F(CoreArbiterServerTest, endArbitration) { 153 | CoreArbiterServer server(socketPath, memPath, {1, 2}, false); 154 | std::thread arbitrationThread([&] { server.startArbitration(); }); 155 | server.endArbitration(); 156 | arbitrationThread.join(); 157 | } 158 | 159 | TEST_F(CoreArbiterServerTest, defaultCores) { 160 | CoreArbiterServer::testingSkipCpusetAllocation = true; 161 | 162 | CoreArbiterServer server(socketPath, memPath, {}, false); 163 | ASSERT_EQ(server.unmanagedCores.size(), 164 | std::thread::hardware_concurrency() - 1); 165 | 166 | CoreArbiterServer::testingSkipCpusetAllocation = false; 167 | } 168 | 169 | TEST_F(CoreArbiterServerTest, threadBlocking_basic) { 170 | CoreArbiterServer::testingSkipCpusetAllocation = true; 171 | CoreArbiterServer::testingSkipSocketCommunication = true; 172 | CoreArbiterServer::testingSkipCoreDistribution = true; 173 | 174 | CoreArbiterServer server(socketPath, memPath, {1}, false); 175 | makeUnmanagedCoresManaged(server); 176 | int processId = 1; 177 | int threadId = 2; 178 | int socket = 3; 179 | ProcessStats processStats; 180 | ProcessInfo* process = createProcess(server, processId, &processStats); 181 | ThreadInfo* thread = createThread(server, threadId, process, socket, 182 | CoreArbiterServer::RUNNING_UNMANAGED); 183 | 184 | // Nothing should happen it the server doesn't know about this thread yet 185 | server.threadSocketToInfo.erase(socket); 186 | server.threadBlocking(socket); 187 | ASSERT_EQ(thread->state, CoreArbiterServer::RUNNING_UNMANAGED); 188 | ASSERT_EQ(processStats.numBlockedThreads, 0u); 189 | 190 | // If the thread is supposed to be blocked already nothing should happen 191 | server.threadSocketToInfo[socket] = thread; 192 | thread->state = CoreArbiterServer::BLOCKED; 193 | server.threadBlocking(socket); 194 | ASSERT_EQ(thread->state, CoreArbiterServer::BLOCKED); 195 | ASSERT_EQ(processStats.numBlockedThreads, 0u); 196 | 197 | // A thread running on the unmanaged core should always be able to block 198 | thread->state = CoreArbiterServer::RUNNING_UNMANAGED; 199 | server.threadBlocking(socket); 200 | ASSERT_EQ(thread->state, CoreArbiterServer::BLOCKED); 201 | ASSERT_EQ(processStats.numBlockedThreads, 1u); 202 | 203 | // If the thread is running on a managed core a block call should fail if 204 | // the server hasn't requested cores back 205 | processStats.numBlockedThreads = 0; 206 | thread->core = server.managedCores[0]; 207 | thread->state = CoreArbiterServer::RUNNING_MANAGED; 208 | server.threadBlocking(socket); 209 | ASSERT_EQ(thread->state, CoreArbiterServer::RUNNING_MANAGED); 210 | ASSERT_EQ(processStats.numBlockedThreads, 0u); 211 | 212 | // If the server has requested cores back, this call succeeds 213 | processStats.threadCommunicationBlocks[server.managedCores[0]->id] 214 | .coreReleaseRequested = true; 215 | server.threadBlocking(socket); 216 | ASSERT_EQ(thread->state, CoreArbiterServer::BLOCKED); 217 | ASSERT_EQ(processStats.numBlockedThreads, 1u); 218 | 219 | CoreArbiterServer::testingSkipCpusetAllocation = false; 220 | CoreArbiterServer::testingSkipSocketCommunication = false; 221 | CoreArbiterServer::testingSkipCoreDistribution = false; 222 | } 223 | 224 | TEST_F(CoreArbiterServerTest, threadBlocking_preemptedThread) { 225 | CoreArbiterServer::testingSkipCpusetAllocation = true; 226 | CoreArbiterServer::testingSkipSocketCommunication = true; 227 | CoreArbiterServer::testingSkipCoreDistribution = true; 228 | CoreArbiterServer::testingDoNotChangeManagedCores = true; 229 | 230 | CoreArbiterServer server(socketPath, memPath, {1}, false); 231 | makeUnmanagedCoresManaged(server); 232 | 233 | pid_t processId = 0; 234 | pid_t threadId = 1; 235 | int socket = 2; 236 | ProcessStats processStats; 237 | processStats.threadCommunicationBlocks[server.managedCores[0]->id] 238 | .coreReleaseRequested = true; 239 | ProcessInfo* process = createProcess(server, processId, &processStats); 240 | ThreadInfo* thread = createThread(server, threadId, process, socket, 241 | CoreArbiterServer::RUNNING_PREEMPTED); 242 | thread->corePreemptedFrom = server.managedCores[0]; 243 | process->coresPreemptedFrom.insert(server.managedCores[0]); 244 | 245 | // A preempted thread blocking counts as releasing a core 246 | server.threadBlocking(socket); 247 | ASSERT_EQ(thread->state, CoreArbiterServer::BLOCKED); 248 | ASSERT_EQ(processStats.preemptedCount, 1u); 249 | ASSERT_EQ(processStats.unpreemptedCount, 1u); 250 | ASSERT_EQ(processStats.numBlockedThreads, 1u); 251 | 252 | CoreArbiterServer::testingSkipCpusetAllocation = false; 253 | CoreArbiterServer::testingSkipSocketCommunication = false; 254 | CoreArbiterServer::testingSkipCoreDistribution = false; 255 | CoreArbiterServer::testingDoNotChangeManagedCores = false; 256 | } 257 | 258 | TEST_F(CoreArbiterServerTest, threadBlocking_movePreemptedThread) { 259 | CoreArbiterServer::testingSkipCpusetAllocation = true; 260 | CoreArbiterServer::testingSkipSocketCommunication = true; 261 | CoreArbiterServer::testingSkipCoreDistribution = true; 262 | CoreArbiterServer::testingDoNotChangeManagedCores = true; 263 | 264 | CoreArbiterServer server(socketPath, memPath, {1}, false); 265 | makeUnmanagedCoresManaged(server); 266 | 267 | pid_t processId = 0; 268 | pid_t threadId1 = 1; 269 | pid_t threadId2 = 2; 270 | int socket1 = 3; 271 | int socket2 = 4; 272 | ProcessStats processStats; 273 | ProcessInfo* process = createProcess(server, processId, &processStats); 274 | ThreadInfo* thread1 = createThread(server, threadId1, process, socket1, 275 | CoreArbiterServer::RUNNING_MANAGED, 276 | server.managedCores[0]); 277 | ThreadInfo* thread2 = createThread(server, threadId2, process, socket2, 278 | CoreArbiterServer::RUNNING_PREEMPTED); 279 | thread2->corePreemptedFrom = server.managedCores[0]; 280 | 281 | // When a thread blocks without being asked to, it is a no-op. 282 | server.threadBlocking(socket1); 283 | ASSERT_EQ(thread1->state, CoreArbiterServer::RUNNING_MANAGED); 284 | ASSERT_EQ(thread2->state, CoreArbiterServer::RUNNING_PREEMPTED); 285 | ASSERT_EQ(process->stats->numOwnedCores, 1u); 286 | 287 | CoreArbiterServer::testingSkipCpusetAllocation = false; 288 | CoreArbiterServer::testingSkipSocketCommunication = false; 289 | CoreArbiterServer::testingSkipCoreDistribution = false; 290 | CoreArbiterServer::testingDoNotChangeManagedCores = false; 291 | } 292 | 293 | TEST_F(CoreArbiterServerTest, coresRequested) { 294 | CoreArbiterServer::testingSkipCpusetAllocation = true; 295 | CoreArbiterServer::testingSkipSocketCommunication = true; 296 | CoreArbiterServer::testingSkipCoreDistribution = true; 297 | CoreArbiterServer::testingDoNotChangeManagedCores = true; 298 | 299 | CoreArbiterServer server(socketPath, memPath, {1}, false); 300 | makeUnmanagedCoresManaged(server); 301 | 302 | ProcessStats processStats; 303 | ProcessInfo* process = createProcess(server, 1, &processStats); 304 | createThread(server, 1, process, serverSocket, 305 | CoreArbiterServer::RUNNING_UNMANAGED); 306 | 307 | // Request 1 core at each priority and make sure this process is on the wait 308 | // list for every priority 309 | std::vector coreRequest = {1, 1, 1, 1, 1, 1, 1, 1}; 310 | send(clientSocket, &coreRequest[0], sizeof(uint32_t) * 8, 0); 311 | server.coresRequested(serverSocket); 312 | for (size_t i = 0; i < coreRequest.size(); i++) { 313 | ASSERT_EQ(server.corePriorityQueues[i].size(), 1u); 314 | ASSERT_EQ(process->desiredCorePriorities[i], coreRequest[i]); 315 | } 316 | 317 | // Adding an additional request shouldn't change anything but the process's 318 | // number of desired cores 319 | coreRequest = {2, 2, 2, 2, 2, 2, 2, 2}; 320 | send(clientSocket, &coreRequest[0], sizeof(uint32_t) * 8, 0); 321 | server.coresRequested(serverSocket); 322 | for (size_t i = 0; i < coreRequest.size(); i++) { 323 | ASSERT_EQ(server.corePriorityQueues[i].size(), 1u); 324 | ASSERT_EQ(process->desiredCorePriorities[i], coreRequest[i]); 325 | } 326 | 327 | // Request fewer cores. This shouldn't change the fact that we're in the 328 | // core priority queue 329 | coreRequest = {2, 2, 2, 2, 1, 1, 1, 1}; 330 | send(clientSocket, &coreRequest[0], sizeof(uint32_t) * 8, 0); 331 | server.coresRequested(serverSocket); 332 | for (size_t i = 0; i < coreRequest.size(); i++) { 333 | ASSERT_EQ(server.corePriorityQueues[i].size(), 1u); 334 | ASSERT_EQ(process->desiredCorePriorities[i], coreRequest[i]); 335 | } 336 | 337 | // Request 0 cores at all priorities. Now we should be removed from all 338 | // priority queues 339 | processStats.numOwnedCores = 4; 340 | coreRequest = {0, 0, 0, 0, 0, 0, 0, 0}; 341 | send(clientSocket, &coreRequest[0], sizeof(uint32_t) * 8, 0); 342 | server.coresRequested(serverSocket); 343 | for (size_t i = 0; i < coreRequest.size(); i++) { 344 | ASSERT_EQ(server.corePriorityQueues[i].size(), 0u); 345 | ASSERT_EQ(process->desiredCorePriorities[i], coreRequest[i]); 346 | } 347 | 348 | CoreArbiterServer::testingSkipCpusetAllocation = false; 349 | CoreArbiterServer::testingSkipSocketCommunication = false; 350 | CoreArbiterServer::testingSkipCoreDistribution = false; 351 | CoreArbiterServer::testingDoNotChangeManagedCores = false; 352 | } 353 | 354 | TEST_F(CoreArbiterServerTest, distributeCores_noBlockedThreads) { 355 | CoreArbiterServer::testingSkipCpusetAllocation = true; 356 | CoreArbiterServer::testingSkipSocketCommunication = true; 357 | CoreArbiterServer::testingDoNotChangeManagedCores = true; 358 | 359 | CoreArbiterServer server(socketPath, memPath, {1, 2, 3}, false); 360 | std::vector processes; 361 | for (int i = 0; i < 2; i++) { 362 | ProcessInfo* process = createProcess(server, i, new ProcessStats()); 363 | processes.push_back(process); 364 | for (int j = 0; j < 2; j++) { 365 | createThread(server, j, process, j, 366 | CoreArbiterServer::RUNNING_UNMANAGED); 367 | } 368 | } 369 | server.corePriorityQueues[7].push_back(processes[0]); 370 | server.corePriorityQueues[7].push_back(processes[1]); 371 | processes[0]->desiredCorePriorities[7] = 2; 372 | processes[1]->desiredCorePriorities[7] = 2; 373 | 374 | server.distributeCores(); 375 | ASSERT_TRUE(server.managedThreads.empty()); 376 | for (CoreInfo* core : server.managedCores) { 377 | ASSERT_EQ(core->managedThread, (ThreadInfo*)NULL); 378 | } 379 | 380 | CoreArbiterServer::testingSkipCpusetAllocation = false; 381 | CoreArbiterServer::testingSkipSocketCommunication = false; 382 | CoreArbiterServer::testingDoNotChangeManagedCores = false; 383 | 384 | for (ProcessInfo* process : processes) { 385 | delete process->stats; 386 | } 387 | } 388 | 389 | TEST_F(CoreArbiterServerTest, distributeCores_niceToHaveSinglePriority) { 390 | CoreArbiterServer::testingSkipCpusetAllocation = true; 391 | CoreArbiterServer::testingSkipSocketCommunication = true; 392 | CoreArbiterServer::testingDoNotChangeManagedCores = true; 393 | 394 | CoreArbiterServer server(socketPath, memPath, {1, 2}, false); 395 | 396 | // Set up two processes who each want two cores at the lowest priority 397 | std::vector processes; 398 | for (int i = 0; i < 2; i++) { 399 | ProcessInfo* process = createProcess(server, i, new ProcessStats()); 400 | processes.push_back(process); 401 | for (int j = 0; j < 2; j++) { 402 | createThread(server, j, process, j, CoreArbiterServer::BLOCKED); 403 | } 404 | } 405 | processes[0]->desiredCorePriorities[7] = 2; 406 | processes[1]->desiredCorePriorities[7] = 2; 407 | server.corePriorityQueues[7].push_back(processes[0]); 408 | server.corePriorityQueues[7].push_back(processes[1]); 409 | 410 | // Cores are shared evenly among nice to have threads of the same priority. 411 | server.distributeCores(); 412 | ASSERT_EQ(server.managedThreads.size(), 2u); 413 | ASSERT_EQ(processes[0]->stats->numOwnedCores, 1u); 414 | ASSERT_EQ(processes[1]->stats->numOwnedCores, 1u); 415 | std::unordered_map savedCoreToThread; 416 | for (CoreInfo* core : server.managedCores) { 417 | ASSERT_TRUE(core->managedThread != NULL); 418 | savedCoreToThread[core] = core->managedThread; 419 | } 420 | 421 | // Threads already running on a managed core are given priority over blocked 422 | // ones in core distribution. 423 | server.distributeCores(); 424 | ASSERT_EQ(server.managedThreads.size(), 2u); 425 | ASSERT_EQ(processes[0]->stats->numOwnedCores, 1u); 426 | ASSERT_EQ(processes[1]->stats->numOwnedCores, 1u); 427 | for (CoreInfo* core : server.managedCores) { 428 | ASSERT_EQ(core->managedThread, savedCoreToThread[core]); 429 | } 430 | 431 | // Don't give processes more cores at this priority than they've asked for 432 | ThreadInfo* removedThread = server.managedCores[0]->managedThread; 433 | removedThread->process->desiredCorePriorities[7] = 0; 434 | server.managedThreads.erase(std::remove(server.managedThreads.begin(), 435 | server.managedThreads.end(), 436 | removedThread)); 437 | server.managedCores[0]->managedThread = NULL; 438 | server.distributeCores(); 439 | ProcessInfo* otherProcess = 440 | removedThread->process == processes[0] ? processes[1] : processes[0]; 441 | ASSERT_EQ(server.managedThreads.size(), 2u); 442 | ASSERT_EQ(otherProcess->stats->numOwnedCores, 2u); 443 | 444 | CoreArbiterServer::testingSkipCpusetAllocation = false; 445 | CoreArbiterServer::testingSkipSocketCommunication = false; 446 | CoreArbiterServer::testingDoNotChangeManagedCores = false; 447 | 448 | for (ProcessInfo* process : processes) { 449 | delete process->stats; 450 | } 451 | } 452 | 453 | TEST_F(CoreArbiterServerTest, distributeCores_niceToHaveMultiplePriorities) { 454 | CoreArbiterServer::testingSkipCpusetAllocation = true; 455 | CoreArbiterServer::testingSkipSocketCommunication = true; 456 | CoreArbiterServer::testingDoNotChangeManagedCores = true; 457 | 458 | CoreArbiterServer server(socketPath, memPath, {1, 2, 3, 4}, false); 459 | 460 | // Set up two processes with four threads each, one requesting at a higher 461 | // nice-to-have priority than the other 462 | std::vector processes; 463 | for (int i = 0; i < 2; i++) { 464 | ProcessInfo* process = createProcess(server, i, new ProcessStats()); 465 | processes.push_back(process); 466 | for (int j = 0; j < 4; j++) { 467 | createThread(server, j, process, j, CoreArbiterServer::BLOCKED); 468 | } 469 | } 470 | ProcessInfo* highPriorityProcess = processes[0]; 471 | ProcessInfo* lowPriorityProcess = processes[1]; 472 | highPriorityProcess->desiredCorePriorities[6] = 3; 473 | lowPriorityProcess->desiredCorePriorities[7] = 3; 474 | server.corePriorityQueues[6].push_back(highPriorityProcess); 475 | server.corePriorityQueues[7].push_back(lowPriorityProcess); 476 | 477 | // Higher priorities are assigned before lower priorities 478 | server.distributeCores(); 479 | ASSERT_EQ(server.managedThreads.size(), 4u); 480 | ASSERT_EQ(highPriorityProcess->stats->numOwnedCores, 3u); 481 | ASSERT_EQ(lowPriorityProcess->stats->numOwnedCores, 1u); 482 | 483 | // Higher priority threads preempt lower priority threads 484 | highPriorityProcess->desiredCorePriorities[6] = 4; 485 | server.distributeCores(); 486 | ASSERT_TRUE(lowPriorityProcess->stats 487 | ->threadCommunicationBlocks[server.managedCores[0]->id] 488 | .coreReleaseRequested || 489 | lowPriorityProcess->stats 490 | ->threadCommunicationBlocks[server.managedCores[1]->id] 491 | .coreReleaseRequested || 492 | lowPriorityProcess->stats 493 | ->threadCommunicationBlocks[server.managedCores[2]->id] 494 | .coreReleaseRequested || 495 | lowPriorityProcess->stats 496 | ->threadCommunicationBlocks[server.managedCores[3]->id] 497 | .coreReleaseRequested); 498 | ASSERT_EQ(server.timerFdToInfo.size(), 1u); 499 | ASSERT_EQ(highPriorityProcess->stats->numOwnedCores, 3u); 500 | 501 | // Higher priority threads aren't placed on a core before the preempted 502 | // thread has timed out 503 | server.distributeCores(); 504 | ASSERT_EQ(highPriorityProcess->stats->numOwnedCores, 3u); 505 | 506 | CoreArbiterServer::testingSkipCpusetAllocation = false; 507 | CoreArbiterServer::testingSkipSocketCommunication = false; 508 | CoreArbiterServer::testingDoNotChangeManagedCores = false; 509 | 510 | for (ProcessInfo* process : processes) { 511 | delete process->stats; 512 | } 513 | } 514 | 515 | TEST_F(CoreArbiterServerTest, distributeCores_scaleUnmanagedCore) { 516 | CoreArbiterServer::testingSkipCpusetAllocation = true; 517 | CoreArbiterServer::testingSkipSocketCommunication = true; 518 | CoreArbiterServer::testingDoNotChangeManagedCores = true; 519 | 520 | CoreArbiterServer server(socketPath, memPath, {1}, false); 521 | 522 | ProcessStats processStats; 523 | ProcessInfo* process = createProcess(server, 1, &processStats); 524 | createThread(server, 1, process, 1, CoreArbiterServer::BLOCKED); 525 | process->desiredCorePriorities[0] = 1; 526 | server.corePriorityQueues[0].push_back(process); 527 | 528 | ASSERT_EQ(server.managedCores.size(), 0u); 529 | ASSERT_EQ(server.unmanagedCores.size(), 1u); 530 | 531 | // Scale up 532 | server.distributeCores(); 533 | ASSERT_EQ(server.managedCores.size(), 1u); 534 | ASSERT_EQ(server.unmanagedCores.size(), 0u); 535 | 536 | // distributeCores() shouldn't cause unmanaged cpuset to scale down 537 | process->desiredCorePriorities[0] = 0; 538 | server.corePriorityQueues[0].pop_front(); 539 | server.managedCores[0]->managedThread = NULL; 540 | server.distributeCores(); 541 | ASSERT_EQ(server.managedCores.size(), 1u); 542 | ASSERT_EQ(server.unmanagedCores.size(), 0u); 543 | 544 | CoreArbiterServer::testingSkipCpusetAllocation = false; 545 | CoreArbiterServer::testingSkipSocketCommunication = false; 546 | CoreArbiterServer::testingDoNotChangeManagedCores = false; 547 | } 548 | 549 | TEST_F(CoreArbiterServerTest, handleEvents_scaleUnmanagedCore) { 550 | CoreArbiterServer::testingSkipCpusetAllocation = true; 551 | CoreArbiterServer::testingSkipSocketCommunication = true; 552 | CoreArbiterServer::testingDoNotChangeManagedCores = true; 553 | 554 | CoreArbiterServer server(socketPath, memPath, {1}, false); 555 | makeUnmanagedCoresManaged(server); 556 | 557 | // The server should wake up to move its unused managed cores to the 558 | // unmanaged cpuset 559 | uint64_t now = Cycles::rdtsc(); 560 | server.unmanagedCpusetLastUpdate = now; 561 | server.managedCores[0]->threadRemovalTime = now; 562 | server.handleEvents(); 563 | ASSERT_EQ(server.managedCores.size(), 0u); 564 | ASSERT_EQ(server.unmanagedCores.size(), 1u); 565 | 566 | CoreArbiterServer::testingSkipCpusetAllocation = false; 567 | CoreArbiterServer::testingSkipSocketCommunication = false; 568 | CoreArbiterServer::testingDoNotChangeManagedCores = false; 569 | } 570 | 571 | TEST_F(CoreArbiterServerTest, timeoutThreadPreemption_basic) { 572 | CoreArbiterServer::testingSkipCpusetAllocation = true; 573 | CoreArbiterServer::testingSkipSocketCommunication = true; 574 | CoreArbiterServer::testingDoNotChangeManagedCores = true; 575 | 576 | CoreArbiterServer server(socketPath, memPath, {1}, false); 577 | makeUnmanagedCoresManaged(server); 578 | server.preemptionTimeout = 1; // For faster testing 579 | 580 | ProcessStats processStats; 581 | CoreInfo* core = server.managedCores[0]; 582 | 583 | ProcessInfo* process = createProcess(server, 1, &processStats); 584 | ThreadInfo* thread = createThread(server, 1, process, 1, 585 | CoreArbiterServer::RUNNING_MANAGED, core); 586 | 587 | // If the client is cooperative, nothing should happen 588 | server.requestCoreRelease(core); 589 | core->managedThread = NULL; 590 | 591 | // Make sure the timer event for preemption is actually processed to avoid 592 | // double-firing on the later handleEvents call. 593 | while (server.timerFdToInfo.size()) 594 | server.handleEvents(); 595 | ASSERT_EQ(thread->state, CoreArbiterServer::RUNNING_MANAGED); 596 | 597 | // If client is uncooperative, the thread should be removed from its core 598 | thread->core = core; 599 | core->managedThread = thread; 600 | server.requestCoreRelease(core); 601 | server.handleEvents(); 602 | ASSERT_EQ(thread->state, CoreArbiterServer::RUNNING_PREEMPTED); 603 | ASSERT_EQ(core->managedThread, (ThreadInfo*)NULL); 604 | ASSERT_EQ(process->stats->numOwnedCores, 0u); 605 | 606 | CoreArbiterServer::testingSkipCpusetAllocation = false; 607 | CoreArbiterServer::testingSkipSocketCommunication = false; 608 | CoreArbiterServer::testingDoNotChangeManagedCores = false; 609 | } 610 | 611 | TEST_F(CoreArbiterServerTest, timeoutThreadPreemption_invalidateOldTimeout) { 612 | CoreArbiterServer::testingSkipCpusetAllocation = true; 613 | CoreArbiterServer::testingSkipSocketCommunication = true; 614 | CoreArbiterServer::testingDoNotChangeManagedCores = true; 615 | 616 | CoreArbiterServer server(socketPath, memPath, {1, 2}, false); 617 | makeUnmanagedCoresManaged(server); 618 | 619 | ProcessStats processStats; 620 | CoreInfo* core = server.managedCores[0]; 621 | 622 | ProcessInfo* process = createProcess(server, 1, &processStats); 623 | ThreadInfo* thread = createThread(server, 1, process, 1, 624 | CoreArbiterServer::RUNNING_MANAGED, core); 625 | 626 | // Simulate a timer going off for a process who previously released a core 627 | server.timerFdToInfo[1] = {1, core}; 628 | core->managedThread = NULL; 629 | server.timeoutThreadPreemption(1); 630 | 631 | ASSERT_EQ(thread->state, CoreArbiterServer::RUNNING_MANAGED); 632 | 633 | CoreArbiterServer::testingSkipCpusetAllocation = false; 634 | CoreArbiterServer::testingSkipSocketCommunication = false; 635 | CoreArbiterServer::testingDoNotChangeManagedCores = false; 636 | } 637 | 638 | TEST_F(CoreArbiterServerTest, cleanupConnection) { 639 | CoreArbiterServer::testingSkipCpusetAllocation = true; 640 | CoreArbiterServer::testingSkipCoreDistribution = true; 641 | CoreArbiterServer::testingDoNotChangeManagedCores = true; 642 | // Prevent close calls since we're not using real sockets 643 | sys->closeErrno = 1; 644 | 645 | CoreArbiterServer server(socketPath, memPath, {1}, false); 646 | makeUnmanagedCoresManaged(server); 647 | 648 | // Set up a process with three threads: one managed, one preempted, and 649 | // one blocked 650 | ProcessStats processStats; 651 | processStats.preemptedCount = 1; 652 | CoreInfo* core = server.managedCores[0]; 653 | ProcessInfo* process = createProcess(server, 1, &processStats); 654 | ThreadInfo* managedThread = createThread( 655 | server, 1, process, 1, CoreArbiterServer::RUNNING_MANAGED, core); 656 | ThreadInfo* preemptedThread = createThread( 657 | server, 2, process, 2, CoreArbiterServer::RUNNING_PREEMPTED); 658 | ThreadInfo* blockedThread = 659 | createThread(server, 3, process, 3, CoreArbiterServer::BLOCKED); 660 | 661 | server.cleanupConnection(managedThread->socket); 662 | ASSERT_TRUE( 663 | process->threadStateToSet[CoreArbiterServer::RUNNING_MANAGED].empty()); 664 | ASSERT_EQ(server.threadSocketToInfo.find(1), 665 | server.threadSocketToInfo.end()); 666 | ASSERT_EQ(std::find(server.managedThreads.begin(), 667 | server.managedThreads.end(), managedThread), 668 | server.managedThreads.end()); 669 | ASSERT_EQ(core->managedThread, (ThreadInfo*)NULL); 670 | ASSERT_EQ(process->stats->numOwnedCores, 0u); 671 | ASSERT_EQ(process->stats->unpreemptedCount, 0u); 672 | ASSERT_EQ(server.processIdToInfo.size(), 1u); 673 | 674 | preemptedThread->corePreemptedFrom = server.managedCores[0]; 675 | process->coresPreemptedFrom.insert(server.managedCores[0]); 676 | server.cleanupConnection(preemptedThread->socket); 677 | ASSERT_TRUE(process->threadStateToSet[CoreArbiterServer::RUNNING_PREEMPTED] 678 | .empty()); 679 | ASSERT_EQ(server.threadSocketToInfo.find(2), 680 | server.threadSocketToInfo.end()); 681 | ASSERT_EQ(process->stats->numOwnedCores, 0u); 682 | ASSERT_EQ(process->stats->unpreemptedCount, 1u); 683 | ASSERT_EQ(server.processIdToInfo.size(), 1u); 684 | 685 | server.cleanupConnection(blockedThread->socket); 686 | ASSERT_EQ(server.threadSocketToInfo.find(3), 687 | server.threadSocketToInfo.end()); 688 | ASSERT_EQ(server.processIdToInfo.size(), 0u); 689 | 690 | CoreArbiterServer::testingSkipCpusetAllocation = false; 691 | CoreArbiterServer::testingSkipCoreDistribution = false; 692 | CoreArbiterServer::testingDoNotChangeManagedCores = false; 693 | sys->closeErrno = 0; 694 | } 695 | 696 | TEST_F(CoreArbiterServerTest, advisoryLock_multiServer) { 697 | CoreArbiterServer server(socketPath, memPath, {1, 2}, false); 698 | ASSERT_DEATH(CoreArbiterServer(socketPath, memPath, {1, 2}, false), 699 | "Error acquiring advisory lock:.*"); 700 | } 701 | } // namespace CoreArbiter 702 | -------------------------------------------------------------------------------- /src/Logger.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017 Stanford University 2 | * 3 | * Permission to use, copy, modify, and distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #include "Logger.h" 17 | 18 | #include 19 | #include "PerfUtils/Cycles.h" 20 | 21 | namespace CoreArbiter { 22 | 23 | using PerfUtils::Cycles; 24 | 25 | FILE* Logger::errorStream = stderr; 26 | LogLevel Logger::displayMinLevel = WARNING; 27 | std::mutex Logger::mutex; 28 | 29 | /** 30 | * Friendly names for each #LogLevel value. 31 | * Keep this in sync with the LogLevel enum. 32 | */ 33 | static const char* logLevelNames[] = {"DEBUG", "NOTICE", "WARNING", "ERROR", 34 | "SILENT"}; 35 | 36 | void 37 | Logger::log(const CodeLocation& where, LogLevel level, const char* fmt, ...) { 38 | if (level < displayMinLevel) { 39 | return; 40 | } 41 | 42 | #define MAX_MESSAGE_CHARS 2000 43 | // Construct a message on the stack and then print it out with the lock 44 | char buffer[MAX_MESSAGE_CHARS]; 45 | int spaceLeft = MAX_MESSAGE_CHARS; 46 | int charsWritten = 0; 47 | int actual; 48 | uint64_t time = Cycles::rdtsc(); 49 | 50 | // Add a header including rdtsc time and location in the file. 51 | actual = snprintf(buffer + charsWritten, spaceLeft, 52 | "%.10lu %s:%d in %s %s: ", time, where.baseFileName(), 53 | where.line, where.function, logLevelNames[level]); 54 | charsWritten += actual; 55 | spaceLeft -= actual; 56 | 57 | // Add the actual message 58 | va_list args; 59 | va_start(args, fmt); 60 | actual = vsnprintf(buffer + charsWritten, spaceLeft, fmt, args); 61 | va_end(args); 62 | 63 | Lock lock(mutex); 64 | fprintf(errorStream, "%s\n", buffer); 65 | fflush(errorStream); 66 | } 67 | 68 | void 69 | Logger::setLogLevel(const char* level) { 70 | if (strcmp(level, "DEBUG") == 0) { 71 | displayMinLevel = DEBUG; 72 | } else if (strcmp(level, "NOTICE") == 0) { 73 | displayMinLevel = NOTICE; 74 | } else if (strcmp(level, "WARNING") == 0) { 75 | displayMinLevel = WARNING; 76 | } else if (strcmp(level, "ERROR") == 0) { 77 | displayMinLevel = ERROR; 78 | } else if (strcmp(level, "SILENT") == 0) { 79 | displayMinLevel = SILENT; 80 | } 81 | } 82 | 83 | } // namespace CoreArbiter 84 | -------------------------------------------------------------------------------- /src/Logger.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017 Stanford University 2 | * 3 | * Permission to use, copy, modify, and distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #ifndef CORE_ARBITER_LOGGER_H 17 | #define CORE_ARBITER_LOGGER_H 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include "CodeLocation.h" 24 | 25 | #define LOG(level, format, ...) \ 26 | do { \ 27 | Logger::log(HERE, level, format, ##__VA_ARGS__); \ 28 | } while (0) 29 | 30 | namespace CoreArbiter { 31 | 32 | /** 33 | * Log levels from most to least inclusive. 34 | */ 35 | enum LogLevel { DEBUG, NOTICE, WARNING, ERROR, SILENT }; 36 | 37 | class Logger { 38 | public: 39 | /** 40 | * Set the minimum severity to print out. 41 | */ 42 | static void setLogLevel(LogLevel level) { displayMinLevel = level; } 43 | 44 | /** 45 | * Set the minimum severity to print out, using a string. 46 | */ 47 | static void setLogLevel(const char* level); 48 | 49 | /** 50 | * Change the target of the error stream, allowing redirection to an 51 | * application's log. 52 | */ 53 | static void setErrorStream(FILE* stream) { errorStream = stream; } 54 | 55 | /** 56 | * Print a message to the console at a given severity level. Accepts 57 | * printf-style format strings. 58 | * 59 | * \param level 60 | * The severity level of this message. 61 | * \param fmt 62 | * A format string, followed by its arguments. 63 | */ 64 | static void log(const CodeLocation& where, LogLevel level, const char* fmt, 65 | ...) __attribute__((format(printf, 3, 4))); 66 | 67 | private: 68 | // The minimum severity level to print. 69 | static LogLevel displayMinLevel; 70 | 71 | // Lock around printing since CoreArbiterClient has threads. 72 | typedef std::unique_lock Lock; 73 | static std::mutex mutex; 74 | 75 | // Used to allow redirection of error messages. 76 | static FILE* errorStream; 77 | }; 78 | 79 | } // namespace CoreArbiter 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /src/MockSyscall.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010-2016 Stanford University 2 | * 3 | * Permission to use, copy, modify, and distribute this software for any purpose 4 | * with or without fee is hereby granted, provided that the above copyright 5 | * notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR ANY 10 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER 11 | * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF 12 | * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #ifndef CORE_ARBITER_MOCK_SYSCALL 17 | #define CORE_ARBITER_MOCK_SYSCALL 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "CoreArbiterCommon.h" 27 | #include "Syscall.h" 28 | 29 | namespace CoreArbiter { 30 | /** 31 | * This class is used as a replacement for Syscall during testing. Each 32 | * method has the same name and interface as a Linux system call; by setting 33 | * variables that preceded the method definition it can be configured to 34 | * do various things such as call the normal system call or return an error. 35 | */ 36 | class MockSyscall : public Syscall { 37 | public: 38 | MockSyscall() 39 | : acceptErrno(0), 40 | bindErrno(0), 41 | chmodErrno(0), 42 | closeErrno(0), 43 | closeCount(0), 44 | closedirErrno(0), 45 | connectErrno(0), 46 | epollCreateErrno(0), 47 | epollCtlErrno(0), 48 | epollWaitCount(-1), 49 | epollWaitEvents(NULL), 50 | epollWaitErrno(0), 51 | exitCount(0), 52 | fcntlErrno(0), 53 | ftruncateErrno(0), 54 | futexWaitErrno(0), 55 | futexWakeErrno(0), 56 | fwriteResult(~0LU), 57 | callGeteuid(true), 58 | geteuidResult(0), 59 | getsocknameErrno(0), 60 | ioctlErrno(0), 61 | ioctlRetriesToSuccess(0), 62 | listenErrno(0), 63 | mkdirErrno(0), 64 | mmapErrno(0), 65 | openErrno(0), 66 | opendirErrno(0), 67 | pipeErrno(0), 68 | readdirErrno(0), 69 | recvErrno(0), 70 | recvEof(false), 71 | recvfromErrno(0), 72 | recvfromEof(false), 73 | recvmmsgErrno(0), 74 | rmdirErrno(0), 75 | sendErrno(0), 76 | sendReturnCount(-1), 77 | sendmsgErrno(0), 78 | sendmsgReturnCount(-1), 79 | sendtoErrno(0), 80 | sendtoReturnCount(-1), 81 | setsockoptErrno(0), 82 | socketErrno(0), 83 | writeErrno(0) {} 84 | 85 | int acceptErrno; 86 | int accept(int sockfd, sockaddr* addr, socklen_t* addrlen) { 87 | if (acceptErrno == 0) { 88 | return ::accept(sockfd, addr, addrlen); 89 | } 90 | errno = acceptErrno; 91 | return -1; 92 | } 93 | 94 | int bindErrno; 95 | int bind(int sockfd, const sockaddr* addr, socklen_t addrlen) { 96 | if (bindErrno == 0) { 97 | return ::bind(sockfd, addr, addrlen); 98 | } 99 | errno = bindErrno; 100 | return -1; 101 | } 102 | 103 | int chmodErrno; 104 | int chmod(const char* path, mode_t mode) { 105 | if (chmodErrno == 0) { 106 | return ::chmod(path, mode); 107 | } 108 | errno = chmodErrno; 109 | return -1; 110 | } 111 | 112 | int closeErrno; 113 | int closeCount; 114 | int close(int fd) { 115 | closeCount++; 116 | if (closeErrno == 0) { 117 | return ::close(fd); 118 | } 119 | errno = closeErrno; 120 | return -1; 121 | } 122 | 123 | int closedirErrno; 124 | int closedir(DIR* dirp) { 125 | if (closedirErrno == 0) { 126 | return ::closedir(dirp); 127 | } 128 | errno = closedirErrno; 129 | return -1; 130 | } 131 | 132 | int connectErrno; 133 | int connect(int sockfd, const sockaddr* addr, socklen_t addrlen) { 134 | if (connectErrno == 0) { 135 | return ::connect(sockfd, addr, addrlen); 136 | } 137 | errno = connectErrno; 138 | return -1; 139 | } 140 | 141 | int epollCreateErrno; 142 | int epoll_create(int size) { 143 | if (epollCreateErrno == 0) { 144 | return ::epoll_create(size); 145 | } 146 | errno = epollCreateErrno; 147 | return -1; 148 | } 149 | 150 | int epollCtlErrno; 151 | int epoll_ctl(int epfd, int op, int fd, epoll_event* event) { 152 | if (epollCtlErrno == 0) { 153 | return ::epoll_ctl(epfd, op, fd, event); 154 | } 155 | errno = epollCtlErrno; 156 | return -1; 157 | } 158 | 159 | int epollWaitCount; 160 | epoll_event* epollWaitEvents; 161 | int epollWaitErrno; 162 | int epoll_wait(int epfd, epoll_event* events, int maxEvents, int timeout) { 163 | if (epollWaitCount >= 0) { 164 | memcpy(events, epollWaitEvents, 165 | epollWaitCount * sizeof(epoll_event)); 166 | int result = epollWaitCount; 167 | epollWaitCount = -1; 168 | return result; 169 | } 170 | if (epollWaitErrno == 0) { 171 | return ::epoll_wait(epfd, events, maxEvents, timeout); 172 | } 173 | errno = epollWaitErrno; 174 | return -1; 175 | } 176 | 177 | int exitCount; 178 | void exit(int status) { 179 | exitCount++; 180 | return; 181 | } 182 | 183 | int fcntlErrno; 184 | int fcntl(int fd, int cmd, int arg1) { 185 | if (fcntlErrno == 0) { 186 | return ::fcntl(fd, cmd, arg1); 187 | } 188 | errno = fcntlErrno; 189 | return -1; 190 | } 191 | 192 | int ftruncateErrno; 193 | int ftruncate(int fd, off_t length) { 194 | if (ftruncateErrno == 0) { 195 | return ::ftruncate(fd, length); 196 | } 197 | errno = ftruncateErrno; 198 | return -1; 199 | } 200 | 201 | int futexWaitErrno; 202 | int futexWait(int* addr, int value) { 203 | if (futexWaitErrno == 0) { 204 | return static_cast( 205 | ::syscall(SYS_futex, addr, FUTEX_WAIT, value, NULL, NULL, 0)); 206 | } 207 | errno = futexWaitErrno; 208 | futexWaitErrno = 0; 209 | return -1; 210 | } 211 | 212 | int futexWakeErrno; 213 | int futexWake(int* addr, int count) { 214 | if (futexWakeErrno == 0) { 215 | return static_cast( 216 | ::syscall(SYS_futex, addr, FUTEX_WAKE, count, NULL, NULL, 0)); 217 | } 218 | errno = futexWakeErrno; 219 | futexWakeErrno = 0; 220 | return -1; 221 | } 222 | 223 | size_t fwriteResult; 224 | size_t fwrite(const void* src, size_t size, size_t count, FILE* f) { 225 | if (fwriteResult == ~0LU) { 226 | return ::fwrite(src, size, count, f); 227 | } 228 | size_t result = fwriteResult; 229 | fwriteResult = ~0LU; 230 | return result; 231 | } 232 | 233 | bool callGeteuid; 234 | int geteuidResult; 235 | uid_t geteuid() { 236 | if (callGeteuid) { 237 | return ::geteuid(); 238 | } 239 | return geteuidResult; 240 | } 241 | 242 | int getsocknameErrno; 243 | int getsockname(int sockfd, sockaddr* addr, socklen_t* addrlen) { 244 | if (getsocknameErrno == 0) { 245 | return ::getsockname(sockfd, addr, addrlen); 246 | } 247 | errno = getsocknameErrno; 248 | getsocknameErrno = 0; 249 | return -1; 250 | } 251 | 252 | int ioctlErrno; 253 | int ioctlRetriesToSuccess; 254 | int ioctl(int fd, int reqType, void* request) { 255 | if (ioctlErrno != 0) { 256 | errno = ioctlErrno; 257 | return -1; 258 | } else if (reqType == SIOCGARP && ioctlRetriesToSuccess > 0) { 259 | // Simulates when kernel ARP cache is busy and not accessible. 260 | ioctlRetriesToSuccess--; 261 | struct arpreq* arpReq = reinterpret_cast(request); 262 | arpReq->arp_flags = 0; 263 | return 0; 264 | } else { 265 | return ::ioctl(fd, reqType, request); 266 | } 267 | } 268 | 269 | int listenErrno; 270 | int listen(int sockfd, int backlog) { 271 | if (listenErrno == 0) { 272 | return ::listen(sockfd, backlog); 273 | } 274 | errno = listenErrno; 275 | return -1; 276 | } 277 | 278 | int mkdirErrno = 0; 279 | int mkdir(const char* pathname, mode_t mode) { 280 | if (mkdirErrno == 0) { 281 | return ::mkdir(pathname, mode); 282 | } 283 | errno = mkdirErrno; 284 | return -1; 285 | } 286 | 287 | int mmapErrno; 288 | void* mmap(void* addr, size_t length, int prot, int flags, int fd, 289 | off_t offset) { 290 | if (mmapErrno == 0) { 291 | return ::mmap(addr, length, prot, flags, fd, offset); 292 | } 293 | errno = mmapErrno; 294 | return MAP_FAILED; 295 | } 296 | 297 | int openErrno; 298 | int open(const char* path, int oflag) { 299 | if (openErrno == 0) { 300 | return ::open(path, oflag); 301 | } 302 | errno = openErrno; 303 | return -1; 304 | } 305 | int open(const char* path, int oflag, mode_t mode) { 306 | if (openErrno == 0) { 307 | return ::open(path, oflag, mode); 308 | } 309 | errno = openErrno; 310 | return -1; 311 | } 312 | 313 | int opendirErrno; 314 | DIR* opendir(const char* name) { 315 | if (opendirErrno == 0) { 316 | return ::opendir(name); 317 | } 318 | errno = opendirErrno; 319 | return NULL; 320 | } 321 | 322 | int pipeErrno; 323 | int pipe(int fds[2]) { 324 | if (pipeErrno == 0) { 325 | return ::pipe(fds); 326 | } 327 | errno = pipeErrno; 328 | return -1; 329 | } 330 | 331 | int readdirErrno; 332 | struct dirent* readdir(DIR* dirp) { 333 | if (readdirErrno == 0) { 334 | return ::readdir(dirp); 335 | } 336 | errno = readdirErrno; 337 | return NULL; 338 | } 339 | 340 | int recvErrno; 341 | bool recvEof; 342 | ssize_t recv(int sockfd, void* buf, size_t len, int flags) { 343 | if (recvEof) { 344 | return 0; 345 | } 346 | if (recvErrno == 0) { 347 | return ::recv(sockfd, buf, len, flags); 348 | } 349 | errno = recvErrno; 350 | return -1; 351 | } 352 | 353 | int recvfromErrno; 354 | bool recvfromEof; 355 | ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags, 356 | sockaddr* from, socklen_t* fromLen) { 357 | if (recvfromEof) { 358 | return 0; 359 | } 360 | if (recvfromErrno == 0) { 361 | return ::recvfrom(sockfd, buf, len, flags, from, fromLen); 362 | } 363 | errno = recvfromErrno; 364 | return -1; 365 | } 366 | 367 | int recvmmsgErrno; 368 | ssize_t recvmmsg(int sockfd, struct mmsghdr* msgvec, unsigned int vlen, 369 | unsigned int flags, struct timespec* timeout) { 370 | if (recvmmsgErrno == 0) { 371 | return ::recvmmsg(sockfd, msgvec, vlen, flags, timeout); 372 | } 373 | errno = recvmmsgErrno; 374 | recvmmsgErrno = 0; 375 | return -1; 376 | } 377 | 378 | int rmdirErrno; 379 | int rmdir(const char* pathname) { 380 | if (rmdirErrno == 0) { 381 | return ::rmdir(pathname); 382 | } 383 | errno = rmdirErrno; 384 | return -1; 385 | } 386 | 387 | int sendErrno; 388 | int sendReturnCount; 389 | ssize_t send(int sockfd, const void* buf, size_t len, int flags) { 390 | if (sendErrno != 0) { 391 | errno = sendErrno; 392 | return -1; 393 | } else if (sendReturnCount >= 0) { 394 | return sendReturnCount; 395 | } 396 | return ::send(sockfd, buf, len, flags); 397 | } 398 | 399 | int sendmsgErrno; 400 | int sendmsgReturnCount; 401 | ssize_t sendmsg(int sockfd, const msghdr* msg, int flags) { 402 | if (sendmsgErrno != 0) { 403 | errno = sendmsgErrno; 404 | return -1; 405 | } else if (sendmsgReturnCount >= 0) { 406 | // Simulates a short-count write. 407 | return sendmsgReturnCount; 408 | } 409 | return ::sendmsg(sockfd, msg, flags); 410 | } 411 | 412 | int sendtoErrno; 413 | int sendtoReturnCount; 414 | ssize_t sendto(int socket, const void* buffer, size_t length, int flags, 415 | const struct sockaddr* destAddr, socklen_t destLen) { 416 | if (sendtoErrno != 0) { 417 | errno = sendtoErrno; 418 | return -1; 419 | } else if (sendtoReturnCount >= 0) { 420 | return sendtoReturnCount; 421 | } 422 | return ::sendto(socket, buffer, length, flags, destAddr, destLen); 423 | } 424 | 425 | int setsockoptErrno; 426 | int setsockopt(int sockfd, int level, int optname, const void* optval, 427 | socklen_t optlen) { 428 | if (setsockoptErrno == 0) { 429 | return ::setsockopt(sockfd, level, optname, optval, optlen); 430 | } 431 | errno = setsockoptErrno; 432 | return -1; 433 | } 434 | 435 | int socketErrno; 436 | int socket(int domain, int type, int protocol) { 437 | if (socketErrno == 0) { 438 | return ::socket(domain, type, protocol); 439 | } 440 | errno = socketErrno; 441 | return -1; 442 | } 443 | 444 | int statErrno; 445 | int stat(const char* path, struct stat* buf) { 446 | if (statErrno == 0) { 447 | ::stat(path, buf); 448 | } 449 | errno = statErrno; 450 | return -1; 451 | } 452 | 453 | int unlinkErrno; 454 | int unlink(const char* pathname) { 455 | if (unlinkErrno == 0) { 456 | ::unlink(pathname); 457 | } 458 | errno = unlinkErrno; 459 | return -1; 460 | } 461 | 462 | int writeErrno; 463 | ssize_t write(int fd, const void* buf, size_t count) { 464 | if (writeErrno == 0) { 465 | return ::write(fd, buf, count); 466 | } 467 | errno = writeErrno; 468 | return -1; 469 | } 470 | }; 471 | 472 | } // namespace CoreArbiter 473 | 474 | #endif // CORE_ARBITER_MOCK_SYSCALL 475 | -------------------------------------------------------------------------------- /src/Semaphore.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017 Stanford University 2 | * 3 | * Permission to use, copy, modify, and distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #ifndef ARACHNE_SEMAPHORE_H 17 | #define ARACHNE_SEMAPHORE_H 18 | 19 | #include 20 | #include 21 | 22 | /** 23 | * This class enables a kernel thread to block in the kernel until a resource 24 | * is available. It is a modified version of 25 | * https://stackoverflow.com/a/4793662/391161. 26 | */ 27 | class Semaphore { 28 | private: 29 | // Protect the internal resource count. 30 | std::mutex mutex; 31 | 32 | // Blocked threads park on this condition variable until this resource is 33 | // available. 34 | std::condition_variable condition; 35 | 36 | // Quantity of resources available for consumption. 37 | uint64_t count = 0; 38 | 39 | // The number of threads which are blocked on this semaphore. 40 | int blocked_count = 0; 41 | 42 | public: 43 | // Restore the resource count to its original state. 44 | void reset() { 45 | std::unique_lock lock(mutex); 46 | count = 0; 47 | } 48 | 49 | // Increase the resource count. 50 | void notify() { 51 | std::unique_lock lock(mutex); 52 | ++count; 53 | condition.notify_one(); 54 | } 55 | 56 | // Block until this resource is available. 57 | void wait() { 58 | std::unique_lock lock(mutex); 59 | while (!count) { // Handle spurious wake-ups. 60 | blocked_count++; 61 | condition.wait(lock); 62 | blocked_count--; 63 | } 64 | --count; 65 | } 66 | 67 | // Attempt to acquire this resource once. 68 | // \return 69 | // Whether or not the acquisition succeeded. inline bool 70 | bool try_wait() { 71 | std::unique_lock lock(mutex); 72 | if (count) { 73 | --count; 74 | return true; 75 | } 76 | return false; 77 | } 78 | 79 | // This method is used in unit tests to determine how many threads are 80 | // blocked on this semaphore. 81 | int get_num_blocked_for_test() { 82 | std::lock_guard lock(mutex); 83 | return blocked_count; 84 | } 85 | }; 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /src/Syscall.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010-2016 Stanford University 2 | * 3 | * Permission to use, copy, modify, and distribute this software for any purpose 4 | * with or without fee is hereby granted, provided that the above copyright 5 | * notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR ANY 10 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER 11 | * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF 12 | * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #ifndef CORE_ARBITER_SYSCALL_H 17 | #define CORE_ARBITER_SYSCALL_H 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | #include "CoreArbiterCommon.h" 35 | 36 | namespace CoreArbiter { 37 | 38 | /** 39 | * This class provides a mechanism for invoking system calls and other 40 | * library functions that makes it easy to intercept the calls with a 41 | * mock class (e.g. MockSyscall) for testing. When system calls are 42 | * invoked through this base class they have the same behavior as if 43 | * they were invoked directly. 44 | * 45 | * The methods have the same names, arguments, and behavior as the 46 | * corresponding Linux/POSIX functions; see the man pages for details. 47 | */ 48 | class Syscall { 49 | public: 50 | Syscall() {} 51 | virtual ~Syscall() {} 52 | 53 | virtual int accept(int sockfd, sockaddr* addr, socklen_t* addrlen) { 54 | return ::accept(sockfd, addr, addrlen); 55 | } 56 | virtual int bind(int sockfd, const sockaddr* addr, socklen_t addrlen) { 57 | return ::bind(sockfd, addr, addrlen); 58 | } 59 | virtual int chmod(const char* path, mode_t mode) { 60 | return ::chmod(path, mode); 61 | } 62 | virtual int close(int fd) { return ::close(fd); } 63 | virtual int closedir(DIR* dirp) { return ::closedir(dirp); } 64 | virtual int connect(int sockfd, const sockaddr* addr, socklen_t addrlen) { 65 | return ::connect(sockfd, addr, addrlen); 66 | } 67 | virtual int epoll_create(int size) { return ::epoll_create(size); } 68 | virtual int epoll_ctl(int epfd, int op, int fd, epoll_event* event) { 69 | return ::epoll_ctl(epfd, op, fd, event); 70 | } 71 | virtual int epoll_wait(int epfd, epoll_event* events, int maxEvents, 72 | int timeout) { 73 | return ::epoll_wait(epfd, events, maxEvents, timeout); 74 | } 75 | virtual void exit(int status) { ::exit(status); } 76 | virtual int ioctl(int fd, int reqType, void* request) { 77 | return ::ioctl(fd, reqType, request); 78 | } 79 | virtual int fcntl(int fd, int cmd, int arg1) { 80 | return ::fcntl(fd, cmd, arg1); 81 | } 82 | virtual FILE* fopen(const char* filename, const char* mode) { 83 | return ::fopen(filename, mode); 84 | } 85 | virtual size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream) { 86 | return ::fread(ptr, size, nmemb, stream); 87 | } 88 | virtual int ftruncate(int fd, off_t length) { 89 | return ::ftruncate(fd, length); 90 | } 91 | virtual int futexWait(int* addr, int value) { 92 | return static_cast( 93 | ::syscall(SYS_futex, addr, FUTEX_WAIT, value, NULL, NULL, 0)); 94 | } 95 | virtual int futexWake(int* addr, int count) { 96 | return static_cast( 97 | ::syscall(SYS_futex, addr, FUTEX_WAKE, count, NULL, NULL, 0)); 98 | } 99 | virtual size_t fwrite(const void* src, size_t size, size_t count, FILE* f) { 100 | return ::fwrite(src, size, count, f); 101 | } 102 | virtual uid_t geteuid() { return ::geteuid(); } 103 | virtual int getsockname(int sockfd, sockaddr* addr, socklen_t* addrlen) { 104 | return ::getsockname(sockfd, addr, addrlen); 105 | } 106 | virtual pid_t gettid() { return (pid_t)syscall(SYS_gettid); } 107 | virtual pid_t getpid() { return ::getpid(); } 108 | virtual int listen(int sockfd, int backlog) { 109 | return ::listen(sockfd, backlog); 110 | } 111 | virtual int mkdir(const char* pathname, mode_t mode) { 112 | return ::mkdir(pathname, mode); 113 | } 114 | virtual void* mmap(void* addr, size_t length, int prot, int flags, int fd, 115 | off_t offset) { 116 | return ::mmap(addr, length, prot, flags, fd, offset); 117 | } 118 | virtual int open(const char* path, int oflag) { 119 | return ::open(path, oflag); 120 | } 121 | virtual int open(const char* path, int oflag, mode_t mode) { 122 | return ::open(path, oflag, mode); 123 | } 124 | virtual DIR* opendir(const char* name) { return ::opendir(name); } 125 | virtual int pipe(int fds[2]) { return ::pipe(fds); } 126 | virtual ssize_t pread(int fd, void* buf, size_t count, off_t offset) { 127 | return ::pread(fd, buf, count, offset); 128 | } 129 | virtual ssize_t pwrite(int fd, const void* buf, size_t count, 130 | off_t offset) { 131 | return ::pwrite(fd, buf, count, offset); 132 | } 133 | virtual struct dirent* readdir(DIR* dirp) { return ::readdir(dirp); } 134 | virtual ssize_t recv(int sockfd, void* buf, size_t len, int flags) { 135 | return ::recv(sockfd, buf, len, flags); 136 | } 137 | virtual ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags, 138 | sockaddr* from, socklen_t* fromLen) { 139 | return ::recvfrom(sockfd, buf, len, flags, from, fromLen); 140 | } 141 | virtual ssize_t recvmmsg(int sockfd, struct mmsghdr* msgvec, 142 | unsigned int vlen, unsigned int flags, 143 | struct timespec* timeout) { 144 | return ::recvmmsg(sockfd, msgvec, vlen, flags, timeout); 145 | } 146 | virtual int rmdir(const char* pathname) { return ::rmdir(pathname); } 147 | virtual int select(int nfds, fd_set* readfds, fd_set* writefds, 148 | fd_set* errorfds, struct timeval* timeout) { 149 | return ::select(nfds, readfds, writefds, errorfds, timeout); 150 | } 151 | virtual ssize_t send(int sockfd, const void* buf, size_t len, int flags) { 152 | return ::send(sockfd, buf, len, flags); 153 | } 154 | virtual ssize_t sendmsg(int sockfd, const msghdr* msg, int flags) { 155 | return ::sendmsg(sockfd, msg, flags); 156 | } 157 | virtual ssize_t sendto(int socket, const void* buffer, size_t length, 158 | int flags, const struct sockaddr* destAddr, 159 | socklen_t destLen) { 160 | return ::sendto(socket, buffer, length, flags, destAddr, destLen); 161 | } 162 | virtual int setsockopt(int sockfd, int level, int optname, 163 | const void* optval, socklen_t optlen) { 164 | return ::setsockopt(sockfd, level, optname, optval, optlen); 165 | } 166 | virtual int socket(int domain, int type, int protocol) { 167 | return ::socket(domain, type, protocol); 168 | } 169 | virtual int stat(const char* path, struct stat* buf) { 170 | return ::stat(path, buf); 171 | } 172 | virtual int timerfd_create(int clockid, int flags) { 173 | return ::timerfd_create(clockid, flags); 174 | } 175 | virtual int timerfd_settime(int fd, int flags, 176 | const struct itimerspec* newVal, 177 | struct itimerspec* oldVal) { 178 | return ::timerfd_settime(fd, flags, newVal, oldVal); 179 | } 180 | virtual int unlink(const char* pathname) { return ::unlink(pathname); } 181 | virtual ssize_t write(int fd, const void* buf, size_t count) { 182 | return ::write(fd, buf, count); 183 | } 184 | virtual ssize_t read(int fd, void* buf, size_t count) { 185 | return ::read(fd, buf, count); 186 | } 187 | 188 | virtual int flock(int fd, int operation) { return ::flock(fd, operation); } 189 | }; 190 | 191 | /** 192 | * Used to set/restore static Syscall* class members for testing. 193 | */ 194 | struct SyscallGuard { 195 | SyscallGuard(Syscall** sys, Syscall* newSys) : sys(sys), old(*sys) { 196 | *sys = newSys; 197 | } 198 | ~SyscallGuard() { *sys = old; } 199 | Syscall** sys; 200 | Syscall* old; 201 | }; 202 | 203 | } // namespace CoreArbiter 204 | 205 | #endif // CORE_ARBITER_SYSCALL_H 206 | -------------------------------------------------------------------------------- /src/mkdir_p.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017 Stanford University 2 | * 3 | * Permission to use, copy, modify, and distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #include 17 | #include /* PATH_MAX */ 18 | #include 19 | #include 20 | #include /* mkdir(2) */ 21 | 22 | /** 23 | * This function ensures that a directory exists, and comes from the github. 24 | * https://gist.github.com/JonathonReinhart/8c0d90191c38af2dcadb102c4e202950 25 | */ 26 | int 27 | mkdir_p(const char* path, mode_t mode) { 28 | // Adapted from http://stackoverflow.com/a/2336245/119527 29 | const size_t len = strlen(path); 30 | char _path[PATH_MAX]; 31 | char* p; 32 | 33 | errno = 0; 34 | 35 | /* Copy string so its mutable */ 36 | if (len > sizeof(_path) - 1) { 37 | errno = ENAMETOOLONG; 38 | return -1; 39 | } 40 | strcpy(_path, path); 41 | 42 | /* Iterate the string */ 43 | for (p = _path + 1; *p; p++) { 44 | if (*p == '/') { 45 | /* Temporarily truncate */ 46 | *p = '\0'; 47 | 48 | if (mkdir(_path, mode) != 0) { 49 | if (errno != EEXIST) 50 | return -1; 51 | } 52 | 53 | *p = '/'; 54 | } 55 | } 56 | 57 | if (mkdir(_path, mode) != 0) { 58 | if (errno != EEXIST) 59 | return -1; 60 | } 61 | return 0; 62 | } 63 | 64 | /** 65 | * Given a path, ensure that the immediate parent of the file specified by the 66 | * path exists. If the path ends in a trailing `/`, it is assumed that we 67 | * want to ensure this directory exists. 68 | */ 69 | int 70 | ensureParents(const char* path, mode_t mode = S_IRWXU) { 71 | char* dup = strdup(path); 72 | const size_t len = strlen(dup); 73 | for (size_t p = len; p > 0; p--) { 74 | if (dup[p] == '/') { 75 | dup[p] = '\0'; 76 | break; 77 | } 78 | } 79 | int retVal = mkdir_p(dup, mode); 80 | free(dup); 81 | return retVal; 82 | } 83 | --------------------------------------------------------------------------------