Added cpu_affinity_map configuration option to bind workers to CPU cores. Allows the admin to place all workers on individual cores without writing a lot of if-statements. For example, cpu_affinity_map process_numbers=1,2,3,4 cores=1,3,5,7 will have an effect for kids 1 through 4 only and will place them on even cores starting with core #1. If there are conflicts for a given process, the latest option wins and a warning is printed. If the number of specified processes do not match the number of specified cores, Squid quits with an error. Multiple cpu_affinity_map options are merged. Squid builds on systems without Linux CPU affinity calls, but setting affinity only works if there are sched_getaffinity(2) and sched_setaffinity(2) available. If there is no OS support but cpu_affinity options are configured, Squid quits with an error. If there is OS support but calls fail, Squid prints an error but does not quit. === modified file 'compat/Makefile.am' --- compat/Makefile.am 2010-08-20 16:15:46 +0000 +++ compat/Makefile.am 2010-09-23 16:57:21 +0000 @@ -14,6 +14,7 @@ assert.h \ compat.h \ compat_shared.h \ + cpu.h \ debug.h \ drand48.h \ eui64_aton.h \ === added file 'compat/cpu.h' --- compat/cpu.h 1970-01-01 00:00:00 +0000 +++ compat/cpu.h 2010-09-23 16:58:11 +0000 @@ -0,0 +1,75 @@ +/* + * $Id$ + */ +#ifndef SQUID_COMPAT_CPU_H +#define SQUID_COMPAT_CPU_H + +#ifndef SQUID_CONFIG_H +#include "config.h" +#endif + +#if HAVE_CPU_AFFINITY +#if HAVE_SCHED_H +#include +#endif + +// glibc prior to 2.6 lacks CPU_COUNT +#ifndef CPU_COUNT +#define CPU_COUNT(set) CpuCount(set) +/// CPU_COUNT replacement +inline int +CpuCount(const cpu_set_t *set) +{ + int count = 0; + for (int i = 0; i < CPU_SETSIZE; ++i) { + if (CPU_ISSET(i, set)) + ++count; + } + return count; +} +#endif /* CPU_COUNT */ + +// glibc prior to 2.7 lacks CPU_AND +#ifndef CPU_AND +#define CPU_AND(destset, srcset1, srcset2) CpuAnd((destset), (srcset1), (srcset2)) +/// CPU_AND replacement +inline void +CpuAnd(cpu_set_t *destset, const cpu_set_t *srcset1, const cpu_set_t *srcset2) +{ + CPU_ZERO(destset); + for (int i = 0; i < CPU_SETSIZE; ++i) { + if (CPU_ISSET(i, srcset1) && CPU_ISSET(i, srcset2)) + CPU_SET(i, destset); + } +} +#endif /* CPU_AND */ + +#else /* HAVE_CPU_AFFINITY */ + +#if HAVE_ERRNO_H +#include /* for ENOTSUP */ +#endif + +/* failing replacements to minimize the number of if-HAVE_CPU_AFFINITYs */ +typedef struct +{ + int bits; +} cpu_set_t; +#define CPU_SETSIZE 0 +#define CPU_COUNT(set) 0 +#define CPU_AND(destset, srcset1, srcset2) (void)0 +#define CPU_ZERO(set) (void)0 +inline int +sched_setaffinity(int, size_t, cpu_set_t *) +{ + return ENOTSUP; +} +inline int +sched_getaffinity(int, size_t, cpu_set_t *) +{ + return ENOTSUP; +} + +#endif /* HAVE_CPU_AFFINITY */ + +#endif /* SQUID_COMPAT_CPU_H */ === modified file 'configure.in' --- configure.in 2010-09-10 15:41:41 +0000 +++ configure.in 2010-09-23 00:07:52 +0000 @@ -2934,6 +2934,8 @@ __res_init \ rint \ sbrk \ + sched_getaffinity \ + sched_setaffinity \ select \ seteuid \ setgroups \ @@ -3008,6 +3010,10 @@ select) AC_DEFINE(USE_SELECT,1,[Use select() for the IO loop]) ;; esac +if test "x$ac_cv_func_sched_getaffinity" = "xyes" -a "x$ac_cv_func_sched_setaffinity" = "xyes" ; then + AC_DEFINE(HAVE_CPU_AFFINITY,1,[Support setting CPU affinity for workers]) +fi + SQUID_CHECK_SETRESUID_WORKS if test "x$squid_cv_resuid_works" = "xyes" ; then AC_DEFINE(HAVE_SETRESUID,1,[Yay! Another Linux brokenness. Knowing that setresuid() exists is not enough, because RedHat 5.0 declares setresuid() but does not implement it.]) === added file 'src/CpuAffinity.cc' --- src/CpuAffinity.cc 1970-01-01 00:00:00 +0000 +++ src/CpuAffinity.cc 2010-09-23 00:07:52 +0000 @@ -0,0 +1,60 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#include "config.h" +#include "base/TextException.h" +#include "CpuAffinity.h" +#include "CpuAffinityMap.h" +#include "CpuAffinitySet.h" +#include "structs.h" + +#include + +static CpuAffinitySet *TheCpuAffinitySet = NULL; + + +void +CpuAffinityInit() +{ + Must(!TheCpuAffinitySet); + if (Config.cpuAffinityMap) { + const int processNumber = InDaemonMode() ? KidIdentifier : 1; + TheCpuAffinitySet = Config.cpuAffinityMap->calculateSet(processNumber); + if (TheCpuAffinitySet) + TheCpuAffinitySet->apply(); + } +} + +void +CpuAffinityReconfigure() +{ + if (TheCpuAffinitySet) { + TheCpuAffinitySet->undo(); + delete TheCpuAffinitySet; + TheCpuAffinitySet = NULL; + } + CpuAffinityInit(); +} + +void +CpuAffinityCheck() +{ + if (Config.cpuAffinityMap) { + Must(!Config.cpuAffinityMap->processes().empty()); + const int maxProcess = + *std::max_element(Config.cpuAffinityMap->processes().begin(), + Config.cpuAffinityMap->processes().end()); + + // in no-deamon mode, there is one process regardless of squid.conf + const int numberOfProcesses = InDaemonMode() ? NumberOfKids() : 1; + + if (maxProcess > numberOfProcesses) { + debugs(54, DBG_IMPORTANT, "WARNING: 'cpu_affinity_map' has " + "non-existing process number(s)"); + } + } +} === added file 'src/CpuAffinity.h' --- src/CpuAffinity.h 1970-01-01 00:00:00 +0000 +++ src/CpuAffinity.h 2010-09-23 05:01:41 +0000 @@ -0,0 +1,21 @@ +/* + * $Id$ + * + */ + +#ifndef SQUID_CPU_AFFINITY_H +#define SQUID_CPU_AFFINITY_H + +#include "config.h" + +/// set CPU affinity for this process on startup +SQUIDCEXTERN void CpuAffinityInit(); + +/// reconfigure CPU affinity for this process +SQUIDCEXTERN void CpuAffinityReconfigure(); + +/// check CPU affinity configuration and print warnings if needed +SQUIDCEXTERN void CpuAffinityCheck(); + + +#endif // SQUID_CPU_AFFINITY_H === added file 'src/CpuAffinityMap.cc' --- src/CpuAffinityMap.cc 1970-01-01 00:00:00 +0000 +++ src/CpuAffinityMap.cc 2010-09-23 05:05:02 +0000 @@ -0,0 +1,59 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#include "config.h" +#include "base/TextException.h" +#include "CpuAffinityMap.h" +#include "CpuAffinitySet.h" +#include "Debug.h" + + +bool +CpuAffinityMap::add(const Vector &aProcesses, const Vector &aCores) +{ + if (aProcesses.size() != aCores.size()) + return false; + + for (size_t i = 0; i < aProcesses.size(); ++i) { + const int process = aProcesses[i]; + const int core = aCores[i]; + if (process <= 0 || core <= 0) + return false; + theProcesses.push_back(process); + theCores.push_back(core); + } + + return true; +} + +CpuAffinitySet * +CpuAffinityMap::calculateSet(const int targetProcess) const +{ + Must(theProcesses.size() == theCores.size()); + int core = 0; + for (size_t i = 0; i < theProcesses.size(); ++i) { + const int process = theProcesses[i]; + if (process == targetProcess) + { + if (core > 0) { + debugs(54, DBG_CRITICAL, "WARNING: conflicting " + "'cpu_affinity_map' for process number " << process << + ", using the last core seen: " << theCores[i]); + } + core = theCores[i]; + } + } + CpuAffinitySet *cpuAffinitySet = NULL; + if (core > 0) { + cpuAffinitySet = new CpuAffinitySet; + cpu_set_t cpuSet; + CPU_ZERO(&cpuSet); + CPU_SET(core - 1, &cpuSet); + cpuAffinitySet->set(cpuSet); + } + return cpuAffinitySet; +} === added file 'src/CpuAffinityMap.h' --- src/CpuAffinityMap.h 1970-01-01 00:00:00 +0000 +++ src/CpuAffinityMap.h 2010-09-23 00:07:52 +0000 @@ -0,0 +1,35 @@ +/* + * $Id$ + * + */ + +#ifndef SQUID_CPU_AFFINITY_MAP_H +#define SQUID_CPU_AFFINITY_MAP_H + +#include "Array.h" + +class CpuAffinitySet; + + +/// stores cpu_affinity_map configuration +class CpuAffinityMap +{ +public: + /// append cpu_affinity_map option + bool add(const Vector &aProcesses, const Vector &aCores); + + /// calculate CPU set for this process + CpuAffinitySet *calculateSet(const int targetProcess) const; + + /// returns list of process numbers + const Vector &processes() const { return theProcesses; } + + /// returns list of cores + const Vector &cores() const { return theCores; } + +private: + Vector theProcesses; ///< list of process numbers + Vector theCores; ///< list of cores +}; + +#endif // SQUID_CPU_AFFINITY_MAP_H === added file 'src/CpuAffinitySet.cc' --- src/CpuAffinitySet.cc 1970-01-01 00:00:00 +0000 +++ src/CpuAffinitySet.cc 2010-09-23 04:54:31 +0000 @@ -0,0 +1,77 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#include "config.h" +#include "base/TextException.h" +#include "CpuAffinitySet.h" +#include "Debug.h" +#include "util.h" + +#if HAVE_STRING_H +#include /* for memcpy() */ +#endif + + +CpuAffinitySet::CpuAffinitySet() +{ + CPU_ZERO(&theCpuSet); + CPU_ZERO(&theOrigCpuSet); +} + +void +CpuAffinitySet::apply() +{ + Must(CPU_COUNT(&theCpuSet) > 0); // CPU affinity mask set + Must(!applied()); + + bool success = false; + if (sched_getaffinity(0, sizeof(theOrigCpuSet), &theOrigCpuSet)) { + debugs(54, DBG_IMPORTANT, "ERROR: failed to get CPU affinity for " + "process PID " << getpid() << ", ignoring CPU affinity for " + "this process: " << xstrerror()); + } else { + cpu_set_t cpuSet; + xmemcpy(&cpuSet, &theCpuSet, sizeof(cpuSet)); + CPU_AND(&cpuSet, &cpuSet, &theOrigCpuSet); + if (CPU_COUNT(&cpuSet) <= 0) { + debugs(54, DBG_IMPORTANT, "ERROR: invalid CPU affinity for process " + "PID " << getpid() << ", may be caused by an invalid core in " + "'cpu_affinity_map' or by external affinity restrictions"); + } else if (sched_setaffinity(0, sizeof(cpuSet), &cpuSet)) { + debugs(54, DBG_IMPORTANT, "ERROR: failed to set CPU affinity for " + "process PID " << getpid() << ": " << xstrerror()); + } else + success = true; + } + if (!success) + CPU_ZERO(&theOrigCpuSet); +} + +void +CpuAffinitySet::undo() +{ + if (applied()) { + if (sched_setaffinity(0, sizeof(theOrigCpuSet), &theOrigCpuSet)) { + debugs(54, DBG_IMPORTANT, "ERROR: failed to restore original CPU " + "affinity for process PID " << getpid() << ": " << + xstrerror()); + } + CPU_ZERO(&theOrigCpuSet); + } +} + +bool +CpuAffinitySet::applied() const +{ + return (CPU_COUNT(&theOrigCpuSet) > 0); +} + +void +CpuAffinitySet::set(const cpu_set_t &aCpuSet) +{ + xmemcpy(&theCpuSet, &aCpuSet, sizeof(theCpuSet)); +} === added file 'src/CpuAffinitySet.h' --- src/CpuAffinitySet.h 1970-01-01 00:00:00 +0000 +++ src/CpuAffinitySet.h 2010-09-23 05:03:16 +0000 @@ -0,0 +1,35 @@ +/* + * $Id$ + * + */ + +#ifndef SQUID_CPU_AFFINITY_SET_H +#define SQUID_CPU_AFFINITY_SET_H + +#include "config.h" +#include "compat/cpu.h" + +/// cpu affinity management for a single process +class CpuAffinitySet +{ +public: + CpuAffinitySet(); + + /// set CPU affinity for this process + void apply(); + + /// undo CPU affinity changes for this process + void undo(); + + /// whether apply() was called and was not undone + bool applied() const; + + /// set CPU affinity mask + void set(const cpu_set_t &aCpuSet); + +private: + cpu_set_t theCpuSet; ///< configured CPU affinity for this process + cpu_set_t theOrigCpuSet; ///< CPU affinity for this process before apply() +}; + +#endif // SQUID_CPU_AFFINITY_SET_H === modified file 'src/Makefile.am' --- src/Makefile.am 2010-09-11 00:52:48 +0000 +++ src/Makefile.am 2010-09-23 00:07:52 +0000 @@ -291,6 +291,12 @@ ConfigParser.cc \ ConfigParser.h \ ConnectionDetail.h \ + CpuAffinity.cc \ + CpuAffinity.h \ + CpuAffinityMap.cc \ + CpuAffinityMap.h \ + CpuAffinitySet.cc \ + CpuAffinitySet.h \ debug.cc \ Debug.h \ defines.h \ @@ -1135,6 +1141,10 @@ $(squid_COMMSOURCES) \ ConfigOption.cc \ ConfigParser.cc \ + CpuAffinityMap.cc \ + CpuAffinityMap.h \ + CpuAffinitySet.cc \ + CpuAffinitySet.h \ $(DELAY_POOL_SOURCE) \ disk.cc \ dlink.h \ @@ -1320,6 +1330,10 @@ $(squid_COMMSOURCES) \ ConfigOption.cc \ ConfigParser.cc \ + CpuAffinityMap.cc \ + CpuAffinityMap.h \ + CpuAffinitySet.cc \ + CpuAffinitySet.h \ $(DELAY_POOL_SOURCE) \ disk.cc \ dlink.h \ @@ -1476,6 +1490,10 @@ $(squid_COMMSOURCES) \ ConfigOption.cc \ ConfigParser.cc \ + CpuAffinityMap.cc \ + CpuAffinityMap.h \ + CpuAffinitySet.cc \ + CpuAffinitySet.h \ $(DELAY_POOL_SOURCE) \ disk.cc \ dlink.h \ @@ -1619,6 +1637,10 @@ $(squid_COMMSOURCES) \ ConfigOption.cc \ ConfigParser.cc \ + CpuAffinityMap.cc \ + CpuAffinityMap.h \ + CpuAffinitySet.cc \ + CpuAffinitySet.h \ tests/stub_main_cc.cc \ debug.cc \ $(DELAY_POOL_SOURCE) \ @@ -1782,6 +1804,10 @@ $(squid_COMMSOURCES) \ ConfigOption.cc \ ConfigParser.cc \ + CpuAffinityMap.cc \ + CpuAffinityMap.h \ + CpuAffinitySet.cc \ + CpuAffinitySet.h \ $(DELAY_POOL_SOURCE) \ disk.cc \ dlink.h \ @@ -2158,6 +2184,10 @@ $(squid_COMMSOURCES) \ ConfigOption.cc \ ConfigParser.cc \ + CpuAffinityMap.cc \ + CpuAffinityMap.h \ + CpuAffinitySet.cc \ + CpuAffinitySet.h \ $(DELAY_POOL_SOURCE) \ disk.cc \ dlink.h \ === modified file 'src/cache_cf.cc' --- src/cache_cf.cc 2010-08-25 03:08:37 +0000 +++ src/cache_cf.cc 2010-09-23 05:06:29 +0000 @@ -50,6 +50,7 @@ #include "auth/Scheme.h" #include "CacheManager.h" #include "ConfigParser.h" +#include "CpuAffinityMap.h" #include "eui/Config.h" #if USE_SQUID_ESI #include "esi/Parser.h" @@ -181,6 +182,12 @@ static void parse_b_size_t(size_t * var); static void parse_b_int64_t(int64_t * var); +static bool parseNamedIntList(const char *data, const String &name, Vector &list); + +static void parse_CpuAffinityMap(CpuAffinityMap **const cpuAffinityMap); +static void dump_CpuAffinityMap(StoreEntry *const entry, const char *const name, const CpuAffinityMap *const cpuAffinityMap); +static void free_CpuAffinityMap(CpuAffinityMap **const cpuAffinityMap); + static int parseOneConfigFile(const char *file_name, unsigned int depth); /* @@ -3842,6 +3849,80 @@ } } +/// parses list of integers form name=N1,N2,N3,... +static bool +parseNamedIntList(const char *data, const String &name, Vector &list) +{ + if (data && (strncmp(data, name.rawBuf(), name.size()) == 0)) { + data += name.size(); + if (*data == '=') { + while (true) { + ++data; + int value = 0; + if (!StringToInt(data, value, &data, 10)) + break; + list.push_back(value); + if (*data == '\0' || *data != ',') + break; + } + } + } + return *data == '\0'; +} + +static void +parse_CpuAffinityMap(CpuAffinityMap **const cpuAffinityMap) { +#if !HAVE_CPU_AFFINITY + debugs(3, DBG_CRITICAL, "FATAL: Squid built with no CPU affinity " << + "support, do not set 'cpu_affinity_map'"); + self_destruct(); +#endif /* HAVE_CPU_AFFINITY */ + + if (!*cpuAffinityMap) + *cpuAffinityMap = new CpuAffinityMap; + + const char *const pToken = strtok(NULL, w_space); + const char *const cToken = strtok(NULL, w_space); + Vector processes, cores; + if (!parseNamedIntList(pToken, "process_numbers", processes)) { + debugs(3, DBG_CRITICAL, "FATAL: bad 'process_numbers' parameter " << + "in 'cpu_affinity_map'"); + self_destruct(); + } else if (!parseNamedIntList(cToken, "cores", cores)) { + debugs(3, DBG_CRITICAL, "FATAL: bad 'cores' parameter in " << + "'cpu_affinity_map'"); + self_destruct(); + } else if (!(*cpuAffinityMap)->add(processes, cores)) { + debugs(3, DBG_CRITICAL, "FATAL: bad 'cpu_affinity_map'; " << + "process_numbers and cores lists differ in length or " << + "contain numbers <= 0"); + self_destruct(); + } +} + +static void +dump_CpuAffinityMap(StoreEntry *const entry, const char *const name, const CpuAffinityMap *const cpuAffinityMap) { + if (cpuAffinityMap) { + storeAppendPrintf(entry, "%s process_numbers=", name); + for (size_t i = 0; i < cpuAffinityMap->processes().size(); ++i) { + storeAppendPrintf(entry, "%s%i", (i ? "," : ""), + cpuAffinityMap->processes()[i]); + } + storeAppendPrintf(entry, " cores="); + for (size_t i = 0; i < cpuAffinityMap->processes().size(); ++i) { + storeAppendPrintf(entry, "%s%i", (i ? "," : ""), + cpuAffinityMap->cores()[i]); + } + storeAppendPrintf(entry, "\n"); + } +} + +static void +free_CpuAffinityMap(CpuAffinityMap **const cpuAffinityMap) { + delete *cpuAffinityMap; + *cpuAffinityMap = NULL; +} + #if USE_ADAPTATION static void === modified file 'src/cf.data.depend' --- src/cf.data.depend 2010-04-05 10:29:50 +0000 +++ src/cf.data.depend 2010-09-23 00:07:52 +0000 @@ -11,6 +11,7 @@ b_size_t cachedir cache_replacement_policy cachemgrpasswd +CpuAffinityMap debug delay_pool_access acl delay_class delay_pool_class delay_pools === modified file 'src/cf.data.pre' --- src/cf.data.pre 2010-09-16 21:43:19 +0000 +++ src/cf.data.pre 2010-09-23 00:07:52 +0000 @@ -7072,4 +7072,26 @@ does (e.g., listen on http_port and forward HTTP requests). DOC_END +NAME: cpu_affinity_map +TYPE: CpuAffinityMap +LOC: Config.cpuAffinityMap +DEFAULT: none +DOC_START + Usage: cpu_affinity_map process_numbers=P1,P2,... cores=C1,C2,... + + Sets 1:1 mapping between Squid processes and CPU cores. For example, + + cpu_affinity_map process_numbers=1,2,3,4 cores=1,3,5,7 + + affects processes 1 through 4 only and places them on the first + four even cores, starting with core #1. + + CPU cores are numbered starting from 1. Requires support for + sched_getaffinity(2) and sched_setaffinity(2) system calls. + + Multiple cpu_affinity_map options are merged. + + See also: workers +DOC_END + EOF === modified file 'src/main.cc' --- src/main.cc 2010-09-08 14:46:42 +0000 +++ src/main.cc 2010-09-23 00:07:52 +0000 @@ -40,6 +40,7 @@ #include "auth/Gadgets.h" #include "base/TextException.h" #include "ConfigParser.h" +#include "CpuAffinity.h" #include "errorpage.h" #include "event.h" #include "EventLoop.h" @@ -770,6 +771,10 @@ Config.workers = oldWorkers; } + if (IamPrimaryProcess()) + CpuAffinityCheck(); + CpuAffinityReconfigure(); + setUmask(Config.umask); Mem::Report(); setEffectiveUser(); @@ -1410,6 +1415,10 @@ return 0; } + if (IamPrimaryProcess()) + CpuAffinityCheck(); + CpuAffinityInit(); + if (!opt_no_daemon && Config.workers > 0) watch_child(argv); === modified file 'src/protos.h' --- src/protos.h 2010-08-24 10:35:03 +0000 +++ src/protos.h 2010-09-23 00:07:52 +0000 @@ -583,8 +583,12 @@ SQUIDCEXTERN bool IamCoordinatorProcess(); /// whether the current process handles HTTP transactions and such SQUIDCEXTERN bool IamWorkerProcess(); +/// Whether we are running in daemon mode +SQUIDCEXTERN bool InDaemonMode(); // try using specific Iam*() checks above first /// Whether there should be more than one worker process running SQUIDCEXTERN bool UsingSmp(); // try using specific Iam*() checks above first +/// number of Kid processes as defined in src/ipc/Kid.h +SQUIDCEXTERN int NumberOfKids(); SQUIDCEXTERN int DebugSignal; /* AYJ debugs function to show locations being reset with memset() */ === modified file 'src/structs.h' --- src/structs.h 2010-09-14 07:45:30 +0000 +++ src/structs.h 2010-09-23 00:07:52 +0000 @@ -130,6 +130,7 @@ /* forward decl for SquidConfig, see RemovalPolicy.h */ +class CpuAffinityMap; class RemovalPolicySettings; class external_acl; class Store; @@ -611,6 +612,7 @@ int umask; int max_filedescriptors; int workers; + CpuAffinityMap *cpuAffinityMap; #if USE_LOADABLE_MODULES wordlist *loadable_module_names; === modified file 'src/tools.cc' --- src/tools.cc 2010-08-09 11:06:36 +0000 +++ src/tools.cc 2010-09-23 00:07:52 +0000 @@ -824,6 +824,12 @@ } bool +InDaemonMode() +{ + return !opt_no_daemon && Config.workers > 0; +} + +bool UsingSmp() { return !opt_no_daemon && Config.workers > 1; @@ -851,6 +857,20 @@ return IamCoordinatorProcess(); } +int +NumberOfKids() +{ + // no kids in no-daemon mode + if (!InDaemonMode()) + return 0; + + // workers + the coordinator process + if (UsingSmp()) + return Config.workers + 1; + + return Config.workers; +} + void writePidFile(void) {