Add 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.
----------
This is take10 of the patch, addressing Amos' comments as discussed below:
On 09/14/2010 12:00 AM, Amos Jeffries wrote:
* since there is a gathering collection of SMP CPU handling bits do you
think its appropriate to add a directory cpu/ to collect these extra SMP
bits? (we have other cpu-specific code for the profiler that might
collect there later.)
** or to at least use base/ for the new code?
Sorry, I do not.
I struggled with the decision where to put this stuff and have
considered both cpu/ and base/. The former seemed premature because
there is too little code for the whole directory and because the scope
would not be clear. The latter seemed wrong because these are not really
base classes that lots of code would use.
Henrik and I discussed your question on the IRC. We did not have the
same opinion as to what the best location is, but we agreed that this
new code should simmer before it is clear where to put it. To minimize
work and confusion, I left it in src/ (Henrik was suggesting
src/staging/ or similar).
* CpuAffinityMap::add does not need to use error variable both the
boolean tests setting error can can return false immediately.
- or they should output debug ERROR/FATAL about the problem.
- note also that regardless of this the case where error gets set
mid-loop theProcesses and/or theCores then gets filled with corrupt data
(n<0).
Fixed. Good catch.
* does cpuAffinityMap = NULL in free_CpuAffinityMap not work? NULL is
not always 0.
Fixed. The code was clearly inconsistent with Squid style.
It is best to use 0 in C++ programs, but we have too much NULL-using
code to fight.
* The #else case in parse_CpuAffinityMap should be a FATAL: since it
aborts the process.
** also, please add a nice FATAL: explanation for the abort in the #if
case. You may need to break the compound if() into several for
self_destruct only prints the annoying "Bungled" message.
Fixed.
IMO, we should be migrating from FATAL to ERROR labels and exceptions in
the parser so that we can nicely skip invalid configurations during
reconfigurations, but that is outside this patch scope.
* NumberofKids does not exactly match its name. NumberOfProcesses()
would be better, or omitting the +1 in SMP mode to actually report the
number of *kids* and adding it explicitly where the coordinator is
needing to be accounted as well.
- doing it the second way the would make CpuAffinityCheck and
CpuAffinityInit only need:
const int numberOfProcesses = NumberOfKids()+1;
Tried to clarify why NumberOfKids() is correct by adding a few source
code comments.
You might be confusing processes with kids with workers, which is not
surprising given the evolving terminology and overlapping scopes.
Happens to me too, and I wrote or designed most of that code! Kids are
processes started by the Master Process. Watch_child in main.cc of the
Master Process is waiting for them. Coordinator process is one of the kids.
Workers are Squid-like processes with no specialized function or powers.
They do what "squid -N" does, essentially. Depending on configuration,
there may be no kids, but there has to be at least one worker process.
I did not want to add a global NumberOfProcesses() function call because
we do not account for many processes (e.g., helpers) and it would not be
clear that the call counts just the Kid processes.
Why the current code pieces seem correct in isolation, I think we will
change the terminology to something simpler and more coherent once we
have more experience with this stuff. Or perhaps it will feel more
coherent with time :-).
* The bits being added to tools.cc and protos.h will need to be pulled
out again very shortly into their own file/lib along with the previous
SMP additions there. The misc unrelated dependencies pulled in by tools
is causing mess.
Agreed. It is just not clear where to put them yet. They are
globally-used tools not specific to IPC code or CPU-management. We could
add something like smp/, but I agree with Henrik that it makes sense to
let this "not yet sure what to do with it" stuff to simmer for a while.
* grumbles about structs.h too. But this is sort of okay since the
global config is a good places for this to be.
Agreed.
Thank you,
Alex.
Add 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 'configure.in'
--- configure.in 2010-09-10 15:41:41 +0000
+++ configure.in 2010-09-17 10:46:01 +0000
@@ -2917,40 +2917,42 @@ AC_CHECK_FUNCS(\
memmove \
memset \
mkstemp \
mktime \
mstats \
poll \
prctl \
pthread_attr_setschedparam \
pthread_attr_setscope \
pthread_setschedparam \
pthread_sigmask \
putenv \
random \
regcomp \
regexec \
regfree \
res_init \
__res_init \
rint \
sbrk \
+ sched_getaffinity \
+ sched_setaffinity \
select \
seteuid \
setgroups \
setpgrp \
setrlimit \
setsid \
sigaction \
snprintf \
socketpair \
srand48 \
srandom \
statfs \
sysconf \
syslog \
timegm \
vsnprintf \
)
dnl ... and some we provide local replacements for
AC_REPLACE_FUNCS(\
drand48 \
@@ -2991,40 +2993,44 @@ else
fi
AC_MSG_NOTICE([Using ${squid_opt_io_loop_engine} for the IO loop.])
AM_CONDITIONAL([USE_POLL], [test $squid_opt_io_loop_engine = poll])
AM_CONDITIONAL([USE_EPOLL], [test $squid_opt_io_loop_engine = epoll])
AM_CONDITIONAL([USE_SELECT], [test $squid_opt_io_loop_engine = select])
AM_CONDITIONAL([USE_SELECT_SIMPLE], [test $squid_opt_io_loop_engine = select_simple])
AM_CONDITIONAL([USE_SELECT_WIN32], [test $squid_opt_io_loop_engine = select_win32])
AM_CONDITIONAL([USE_KQUEUE], [test $squid_opt_io_loop_engine = kqueue])
AM_CONDITIONAL([USE_DEVPOLL], [test $squid_opt_io_loop_engine = devpoll])
case $squid_opt_io_loop_engine in
epoll) AC_DEFINE(USE_EPOLL,1,[Use epoll() for the IO loop]) ;;
poll) AC_DEFINE(USE_POLL,1,[Use poll() for the IO loop]) ;;
kqueue) AC_DEFINE(USE_KQUEUE,1,[Use kqueue() for the IO loop]) ;;
select_win32) AC_DEFINE(USE_SELECT_WIN32,1,[Use Winsock select() for the IO loop]) ;;
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.])
fi
SQUID_CHECK_FUNC_STRNSTR
SQUID_CHECK_FUNC_VACOPY
SQUID_CHECK_FUNC___VACOPY
dnl IP-Filter support requires ipf header files. These aren't
dnl installed by default, so we need to check for them
if test "x$enable_ipf_transparent" != "xno" ; then
AC_MSG_CHECKING(for availability of IP-Filter header files)
# hold on to your hats...
if test "x$ac_cv_header_ip_compat_h" = "xyes" -o \
"x$ac_cv_header_ip_fil_compat_h" = "xyes" -o \
"x$ac_cv_header_netinet_ip_compat_h" = "xyes" -o \
"x$ac_cv_header_netinet_ip_fil_compat_h" = "xyes" ; then
have_ipfilter_compat_header="yes"
=== added file 'src/CpuAffinity.cc'
--- src/CpuAffinity.cc 1970-01-01 00:00:00 +0000
+++ src/CpuAffinity.cc 2010-09-17 11:05:34 +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 <algorithm>
+
+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-17 10:46:01 +0000
@@ -0,0 +1,20 @@
+/*
+ * $Id$
+ *
+ */
+
+#ifndef SQUID_CPU_AFFINITY_H
+#define SQUID_CPU_AFFINITY_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-17 11:05:49 +0000
@@ -0,0 +1,61 @@
+/*
+ * $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<int> &aProcesses, const Vector<int> &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 HAVE_CPU_AFFINITY
+ if (core > 0) {
+ cpuAffinitySet = new CpuAffinitySet;
+ cpu_set_t cpuSet;
+ CPU_ZERO(&cpuSet);
+ CPU_SET(core - 1, &cpuSet);
+ cpuAffinitySet->set(cpuSet);
+ }
+#endif
+ return cpuAffinitySet;
+}
=== added file 'src/CpuAffinityMap.h'
--- src/CpuAffinityMap.h 1970-01-01 00:00:00 +0000
+++ src/CpuAffinityMap.h 2010-09-17 10:46:49 +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<int> &aProcesses, const Vector<int> &aCores);
+
+ /// calculate CPU set for this process
+ CpuAffinitySet *calculateSet(const int targetProcess) const;
+
+ /// returns list of process numbers
+ const Vector<int> &processes() const { return theProcesses; }
+
+ /// returns list of cores
+ const Vector<int> &cores() const { return theCores; }
+
+private:
+ Vector<int> theProcesses; ///< list of process numbers
+ Vector<int> 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-17 10:46:01 +0000
@@ -0,0 +1,85 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54 Interprocess Communication
+ *
+ */
+
+#include "config.h"
+#include "base/TextException.h"
+#include "CpuAffinitySet.h"
+#include "Debug.h"
+#include "util.h"
+
+
+CpuAffinitySet::CpuAffinitySet()
+{
+#if HAVE_CPU_AFFINITY
+ CPU_ZERO(&theCpuSet);
+ CPU_ZERO(&theOrigCpuSet);
+#endif
+}
+
+void
+CpuAffinitySet::apply()
+{
+#if HAVE_CPU_AFFINITY
+ 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);
+#endif
+}
+
+void
+CpuAffinitySet::undo()
+{
+#if HAVE_CPU_AFFINITY
+ 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);
+ }
+#endif
+}
+
+bool
+CpuAffinitySet::applied() const
+{
+#if HAVE_CPU_AFFINITY
+ return (CPU_COUNT(&theOrigCpuSet) > 0);
+#else
+ return false;
+#endif
+}
+
+#if HAVE_CPU_AFFINITY
+void
+CpuAffinitySet::set(const cpu_set_t &aCpuSet)
+{
+ xmemcpy(&theCpuSet, &aCpuSet, sizeof(theCpuSet));
+}
+#endif
=== added file 'src/CpuAffinitySet.h'
--- src/CpuAffinitySet.h 1970-01-01 00:00:00 +0000
+++ src/CpuAffinitySet.h 2010-09-17 10:47:14 +0000
@@ -0,0 +1,41 @@
+/*
+ * $Id$
+ *
+ */
+
+#ifndef SQUID_CPU_AFFINITY_SET_H
+#define SQUID_CPU_AFFINITY_SET_H
+
+#include "SquidString.h"
+
+#if HAVE_CPU_AFFINITY && HAVE_SCHED_H
+#include <sched.h>
+#endif
+
+
+/// 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;
+
+#if HAVE_CPU_AFFINITY
+ /// 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
+};
+
+#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-17 10:46:01 +0000
@@ -274,40 +274,46 @@ squid_SOURCES = \
client_side_reply.cc \
client_side_reply.h \
client_side_request.cc \
client_side_request.h \
ClientInfo.h \
BodyPipe.cc \
BodyPipe.h \
ClientInfo.h \
ClientRequestContext.h \
clientStream.cc \
clientStream.h \
CommIO.h \
CompletionDispatcher.cc \
CompletionDispatcher.h \
$(squid_COMMSOURCES) \
CommRead.h \
ConfigOption.cc \
ConfigParser.cc \
ConfigParser.h \
ConnectionDetail.h \
+ CpuAffinity.cc \
+ CpuAffinity.h \
+ CpuAffinityMap.cc \
+ CpuAffinityMap.h \
+ CpuAffinitySet.cc \
+ CpuAffinitySet.h \
debug.cc \
Debug.h \
defines.h \
$(DELAY_POOL_SOURCE) \
disk.cc \
$(DISKIO_SOURCE) \
dlink.h \
dlink.cc \
$(DNSSOURCE) \
enums.h \
err_type.h \
errorpage.cc \
errorpage.h \
ETag.cc \
event.cc \
event.h \
EventLoop.h \
EventLoop.cc \
external_acl.cc \
ExternalACL.h \
@@ -1118,40 +1124,44 @@ tests_testCacheManager_SOURCES = \
tests/stub_main_cc.cc \
time.cc \
BodyPipe.cc \
cache_manager.cc \
cache_cf.cc \
ProtoPort.cc \
ProtoPort.h \
CacheDigest.cc \
carp.cc \
cbdata.cc \
ChunkedCodingParser.cc \
client_db.cc \
client_side.cc \
client_side_reply.cc \
client_side_request.cc \
ClientInfo.h \
clientStream.cc \
$(squid_COMMSOURCES) \
ConfigOption.cc \
ConfigParser.cc \
+ CpuAffinityMap.cc \
+ CpuAffinityMap.h \
+ CpuAffinitySet.cc \
+ CpuAffinitySet.h \
$(DELAY_POOL_SOURCE) \
disk.cc \
dlink.h \
dlink.cc \
$(DNSSOURCE) \
errorpage.cc \
ETag.cc \
external_acl.cc \
ExternalACLEntry.cc \
fd.cc \
fde.cc \
forward.cc \
fqdncache.cc \
ftp.cc \
gopher.cc \
hier_code.h \
helper.cc \
HelperChildConfig.h \
HelperChildConfig.cc \
$(HTCPSOURCE) \
@@ -1303,40 +1313,44 @@ tests_testEvent_SOURCES = \
tests/stub_main_cc.cc \
time.cc \
BodyPipe.cc \
cache_manager.cc \
cache_cf.cc \
ProtoPort.cc \
ProtoPort.h \
CacheDigest.cc \
carp.cc \
cbdata.cc \
ChunkedCodingParser.cc \
client_db.cc \
client_side.cc \
client_side_reply.cc \
client_side_request.cc \
ClientInfo.h \
clientStream.cc \
$(squid_COMMSOURCES) \
ConfigOption.cc \
ConfigParser.cc \
+ CpuAffinityMap.cc \
+ CpuAffinityMap.h \
+ CpuAffinitySet.cc \
+ CpuAffinitySet.h \
$(DELAY_POOL_SOURCE) \
disk.cc \
dlink.h \
dlink.cc \
$(DNSSOURCE) \
errorpage.cc \
ETag.cc \
external_acl.cc \
ExternalACLEntry.cc \
fd.cc \
fde.cc \
forward.cc \
fqdncache.cc \
ftp.cc \
gopher.cc \
hier_code.h \
helper.cc \
HelperChildConfig.h \
HelperChildConfig.cc \
$(HTCPSOURCE) \
@@ -1459,40 +1473,44 @@ tests_testEventLoop_SOURCES = \
tests/stub_main_cc.cc \
time.cc \
BodyPipe.cc \
cache_manager.cc \
cache_cf.cc \
ProtoPort.cc \
ProtoPort.h \
CacheDigest.cc \
carp.cc \
cbdata.cc \
ChunkedCodingParser.cc \
client_db.cc \
client_side.cc \
client_side_reply.cc \
client_side_request.cc \
ClientInfo.h \
clientStream.cc \
$(squid_COMMSOURCES) \
ConfigOption.cc \
ConfigParser.cc \
+ CpuAffinityMap.cc \
+ CpuAffinityMap.h \
+ CpuAffinitySet.cc \
+ CpuAffinitySet.h \
$(DELAY_POOL_SOURCE) \
disk.cc \
dlink.h \
dlink.cc \
$(DNSSOURCE) \
errorpage.cc \
ETag.cc \
external_acl.cc \
ExternalACLEntry.cc \
fd.cc \
fde.cc \
forward.cc \
fqdncache.cc \
ftp.cc \
gopher.cc \
helper.cc \
HelperChildConfig.h \
HelperChildConfig.cc \
hier_code.h \
$(HTCPSOURCE) \
@@ -1602,40 +1620,44 @@ tests_test_http_range_SOURCES = \
$(ACL_REGISTRATION_SOURCES) \
tests/test_http_range.cc \
BodyPipe.cc \
cache_cf.cc \
ProtoPort.cc \
ProtoPort.h \
cache_manager.cc \
CacheDigest.cc \
carp.cc \
cbdata.cc \
ChunkedCodingParser.cc \
client_db.cc \
client_side.cc \
client_side_reply.cc \
client_side_request.cc \
ClientInfo.h \
clientStream.cc \
$(squid_COMMSOURCES) \
ConfigOption.cc \
ConfigParser.cc \
+ CpuAffinityMap.cc \
+ CpuAffinityMap.h \
+ CpuAffinitySet.cc \
+ CpuAffinitySet.h \
tests/stub_main_cc.cc \
debug.cc \
$(DELAY_POOL_SOURCE) \
disk.cc \
dlink.h \
dlink.cc \
$(DNSSOURCE) \
errorpage.cc \
ETag.cc \
external_acl.cc \
ExternalACLEntry.cc \
fd.cc \
fde.cc \
forward.cc \
fqdncache.cc \
ftp.cc \
gopher.cc \
helper.cc \
HelperChildConfig.h \
HelperChildConfig.cc \
@@ -1765,40 +1787,44 @@ tests_testHttpRequest_SOURCES = \
tests/stub_main_cc.cc \
time.cc \
BodyPipe.cc \
cache_manager.cc \
cache_cf.cc \
ProtoPort.cc \
ProtoPort.h \
CacheDigest.cc \
carp.cc \
cbdata.cc \
ChunkedCodingParser.cc \
client_db.cc \
client_side.cc \
client_side_reply.cc \
client_side_request.cc \
ClientInfo.h \
clientStream.cc \
$(squid_COMMSOURCES) \
ConfigOption.cc \
ConfigParser.cc \
+ CpuAffinityMap.cc \
+ CpuAffinityMap.h \
+ CpuAffinitySet.cc \
+ CpuAffinitySet.h \
$(DELAY_POOL_SOURCE) \
disk.cc \
dlink.h \
dlink.cc \
$(DNSSOURCE) \
errorpage.cc \
ETag.cc \
external_acl.cc \
ExternalACLEntry.cc \
fd.cc \
fde.cc \
forward.cc \
fqdncache.cc \
ftp.cc \
gopher.cc \
helper.cc \
HelperChildConfig.h \
HelperChildConfig.cc \
hier_code.h \
$(HTCPSOURCE) \
@@ -2141,40 +2167,44 @@ tests_testURL_SOURCES = \
tests/stub_main_cc.cc \
time.cc \
BodyPipe.cc \
cache_manager.cc \
cache_cf.cc \
ProtoPort.cc \
ProtoPort.h \
CacheDigest.cc \
carp.cc \
cbdata.cc \
ChunkedCodingParser.cc \
client_db.cc \
client_side.cc \
client_side_reply.cc \
client_side_request.cc \
ClientInfo.h \
clientStream.cc \
$(squid_COMMSOURCES) \
ConfigOption.cc \
ConfigParser.cc \
+ CpuAffinityMap.cc \
+ CpuAffinityMap.h \
+ CpuAffinitySet.cc \
+ CpuAffinitySet.h \
$(DELAY_POOL_SOURCE) \
disk.cc \
dlink.h \
dlink.cc \
$(DNSSOURCE) \
errorpage.cc \
ETag.cc \
external_acl.cc \
ExternalACLEntry.cc \
fd.cc \
fde.cc \
forward.cc \
fqdncache.cc \
ftp.cc \
gopher.cc \
helper.cc \
HelperChildConfig.h \
HelperChildConfig.cc \
hier_code.h \
$(HTCPSOURCE) \
=== modified file 'src/cache_cf.cc'
--- src/cache_cf.cc 2010-08-25 03:08:37 +0000
+++ src/cache_cf.cc 2010-09-17 11:32:32 +0000
@@ -33,40 +33,41 @@
*/
#include "squid.h"
#include "acl/Acl.h"
#include "acl/Gadgets.h"
#include "acl/MethodData.h"
#if USE_ADAPTATION
#include "adaptation/Config.h"
#endif
#if ICAP_CLIENT
#include "adaptation/icap/Config.h"
#endif
#if USE_ECAP
#include "adaptation/ecap/Config.h"
#endif
#include "auth/Config.h"
#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"
#endif
#include "HttpRequestMethod.h"
#include "ident/Config.h"
#include "ip/Intercept.h"
#include "ip/QosConfig.h"
#include "ip/tools.h"
#include "log/Config.h"
#include "MemBuf.h"
#include "Parsing.h"
#include "ProtoPort.h"
#include "rfc1738.h"
#if SQUID_SNMP
#include "snmp.h"
#endif
#include "Store.h"
#include "StoreFileSystem.h"
#include "SwapDir.h"
@@ -164,40 +165,45 @@ static void free_IpAddress_list(Ip::Addr
static int check_null_IpAddress_list(const Ip::Address_list *);
#endif /* CURRENTLY_UNUSED */
#endif /* USE_WCCPv2 */
static void parse_http_port_list(http_port_list **);
static void dump_http_port_list(StoreEntry *, const char *, const http_port_list *);
static void free_http_port_list(http_port_list **);
#if USE_SSL
static void parse_https_port_list(https_port_list **);
static void dump_https_port_list(StoreEntry *, const char *, const https_port_list *);
static void free_https_port_list(https_port_list **);
#if 0
static int check_null_https_port_list(const https_port_list *);
#endif
#endif /* USE_SSL */
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<int> &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);
/*
* LegacyParser is a parser for legacy code that uses the global
* approach. This is static so that it is only exposed to cache_cf.
* Other modules needing access to a ConfigParser should have it
* provided to them in their parserFOO methods.
*/
static ConfigParser LegacyParser = ConfigParser();
void
self_destruct(void)
{
LegacyParser.destruct();
}
static void
update_maxobjsize(void)
{
int i;
@@ -3825,40 +3831,113 @@ free_logformat(logformat ** definitions)
static void
free_access_log(customlog ** definitions)
{
while (*definitions) {
customlog *log = *definitions;
*definitions = log->next;
log->logFormat = NULL;
log->type = CLF_UNKNOWN;
if (log->aclList)
aclDestroyAclList(&log->aclList);
safe_free(log->filename);
xfree(log);
}
}
+/// parses list of integers form name=N1,N2,N3,...
+static bool
+parseNamedIntList(const char *data, const String &name, Vector<int> &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
+ if (!*cpuAffinityMap)
+ *cpuAffinityMap = new CpuAffinityMap;
+
+ const char *const pToken = strtok(NULL, w_space);
+ const char *const cToken = strtok(NULL, w_space);
+ Vector<int> 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();
+ }
+#else
+ debugs(3, DBG_CRITICAL, "FATAL: Squid built with no CPU affinity " <<
+ "support, do not set 'cpu_affinity_map'");
+ self_destruct();
+#endif
+}
+
+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
parse_adaptation_service_set_type()
{
Adaptation::Config::ParseServiceSet();
}
static void
parse_adaptation_service_chain_type()
{
Adaptation::Config::ParseServiceChain();
}
static void
parse_adaptation_access_type()
{
Adaptation::Config::ParseAccess(LegacyParser);
}
=== modified file 'src/cf.data.depend'
--- src/cf.data.depend 2010-04-05 10:29:50 +0000
+++ src/cf.data.depend 2010-09-17 10:46:01 +0000
@@ -1,33 +1,34 @@
# type dependencies
access_log acl logformat
acl external_acl_type auth_param
acl_access acl
acl_address acl
acl_b_size_t acl
acl_tos acl
address
authparam
b_int64_t
b_size_t
cachedir cache_replacement_policy
cachemgrpasswd
+CpuAffinityMap
debug
delay_pool_access acl delay_class
delay_pool_class delay_pools
delay_pool_count
delay_pool_rates delay_class
denyinfo acl
eol
externalAclHelper auth_param
HelperChildConfig
hostdomain cache_peer
hostdomaintype cache_peer
http_header_access acl
http_header_replace
http_port_list
https_port_list
adaptation_access_type adaptation_service_set adaptation_service_chain acl icap_service icap_class
adaptation_service_set_type icap_service ecap_service
adaptation_service_chain_type icap_service ecap_service
icap_access_type icap_class acl
icap_class_type icap_service
=== modified file 'src/cf.data.pre'
--- src/cf.data.pre 2010-09-16 21:43:19 +0000
+++ src/cf.data.pre 2010-09-17 10:46:01 +0000
@@ -7055,21 +7055,43 @@ DOC_START
The default "0" means Squid inherits the current ulimit setting.
Note: Changing this requires a restart of Squid. Also
not all comm loops supports large values.
DOC_END
NAME: workers
TYPE: int
LOC: Config.workers
DEFAULT: 1
DOC_START
Number of main Squid processes or "workers" to fork and maintain.
0: "no daemon" mode, like running "squid -N ..."
1: "no SMP" mode, start one main Squid process daemon (default)
N: start N main Squid process daemons (i.e., SMP mode)
In SMP mode, each worker does nearly all what a single Squid daemon
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,...
+
+ Set 1:1 mapping between Squid "worker" processes and CPU cores. E.g.,
+
+ 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 place them on
+ even cores starting with core #1.
+
+ CPU cores are numbered starting from 1. Requires 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-17 10:46:01 +0000
@@ -23,40 +23,41 @@
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
*
*/
#include "squid.h"
#include "AccessLogEntry.h"
#if ICAP_CLIENT
#include "adaptation/icap/icap_log.h"
#endif
#include "auth/Gadgets.h"
#include "base/TextException.h"
#include "ConfigParser.h"
+#include "CpuAffinity.h"
#include "errorpage.h"
#include "event.h"
#include "EventLoop.h"
#include "ExternalACL.h"
#include "Store.h"
#include "ICP.h"
#include "ident/Ident.h"
#include "HttpReply.h"
#include "pconn.h"
#include "Mem.h"
#include "acl/Asn.h"
#include "acl/Acl.h"
#include "htcp.h"
#include "StoreFileSystem.h"
#include "DiskIO/DiskIOModule.h"
#include "comm.h"
#include "ipc/Kids.h"
#include "ipc/Coordinator.h"
#include "ipc/Strand.h"
#include "ip/tools.h"
@@ -753,40 +754,44 @@ mainReconfigureFinish(void *)
errorClean();
enter_suid(); /* root to read config file */
// we may have disabled the need for PURGE
if (Config2.onoff.enable_purge)
Config2.onoff.enable_purge = 2;
// parse the config returns a count of errors encountered.
const int oldWorkers = Config.workers;
if ( parseConfigFile(ConfigFile) != 0) {
// for now any errors are a fatal condition...
self_destruct();
}
if (oldWorkers != Config.workers) {
debugs(1, DBG_CRITICAL, "WARNING: Changing 'workers' (from " <<
oldWorkers << " to " << Config.workers <<
") is not supported and ignored");
Config.workers = oldWorkers;
}
+ if (IamPrimaryProcess())
+ CpuAffinityCheck();
+ CpuAffinityReconfigure();
+
setUmask(Config.umask);
Mem::Report();
setEffectiveUser();
_db_init(Debug::cache_log, Debug::debugOptions);
ipcache_restart(); /* clear stuck entries */
fqdncache_restart(); /* sigh, fqdncache too */
parseEtcHosts();
errorInitialize(); /* reload error pages */
accessLogInit();
#if USE_LOADABLE_MODULES
LoadableModulesConfigure(Config.loadable_module_names);
#endif
#if USE_ADAPTATION
bool enableAdaptation = false;
#if ICAP_CLIENT
Adaptation::Icap::TheConfig.finalize();
enableAdaptation = Adaptation::Icap::TheConfig.onoff || enableAdaptation;
#endif
@@ -1393,40 +1398,44 @@ SquidMain(int argc, char **argv)
}
sendSignal();
/* NOTREACHED */
}
if (opt_create_swap_dirs) {
/* chroot if configured to run inside chroot */
if (Config.chroot_dir && chroot(Config.chroot_dir)) {
fatal("failed to chroot");
}
setEffectiveUser();
debugs(0, 0, "Creating Swap Directories");
Store::Root().create();
return 0;
}
+ if (IamPrimaryProcess())
+ CpuAffinityCheck();
+ CpuAffinityInit();
+
if (!opt_no_daemon && Config.workers > 0)
watch_child(argv);
setMaxFD();
/* init comm module */
comm_init();
comm_select_init();
if (opt_no_daemon) {
/* we have to init fdstat here. */
fd_open(0, FD_LOG, "stdin");
fd_open(1, FD_LOG, "stdout");
fd_open(2, FD_LOG, "stderr");
}
#if USE_WIN32_SERVICE
WIN32_svcstatusupdate(SERVICE_START_PENDING, 10000);
=== modified file 'src/protos.h'
--- src/protos.h 2010-08-24 10:35:03 +0000
+++ src/protos.h 2010-09-17 10:59:33 +0000
@@ -566,42 +566,46 @@ SQUIDCEXTERN void enter_suid(void);
SQUIDCEXTERN void no_suid(void);
SQUIDCEXTERN void writePidFile(void);
SQUIDCEXTERN void setSocketShutdownLifetimes(int);
SQUIDCEXTERN void setMaxFD(void);
SQUIDCEXTERN void setSystemLimits(void);
SQUIDCEXTERN void squid_signal(int sig, SIGHDLR *, int flags);
SQUIDCEXTERN pid_t readPidFile(void);
SQUIDCEXTERN void keepCapabilities(void);
SQUIDCEXTERN void BroadcastSignalIfAny(int& sig);
/// whether the current process is the parent of all other Squid processes
SQUIDCEXTERN bool IamMasterProcess();
/**
whether the current process is dedicated to doing things that only
a single process should do, such as PID file maintenance and WCCP
*/
SQUIDCEXTERN bool IamPrimaryProcess();
/// whether the current process coordinates worker processes
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() */
SQUIDCEXTERN void *xmemset(void *dst, int, size_t);
SQUIDCEXTERN void debug_trap(const char *);
SQUIDCEXTERN void logsFlush(void);
SQUIDCEXTERN const char *checkNullString(const char *p);
SQUIDCEXTERN void squid_getrusage(struct rusage *r);
SQUIDCEXTERN double rusage_cputime(struct rusage *r);
SQUIDCEXTERN int rusage_maxrss(struct rusage *r);
SQUIDCEXTERN int rusage_pagefaults(struct rusage *r);
SQUIDCEXTERN void releaseServerSockets(void);
SQUIDCEXTERN void PrintRusage(void);
SQUIDCEXTERN void dumpMallocStats(void);
=== modified file 'src/structs.h'
--- src/structs.h 2010-09-14 07:45:30 +0000
+++ src/structs.h 2010-09-17 10:46:01 +0000
@@ -113,40 +113,41 @@ struct ushortlist {
};
struct relist {
char *pattern;
regex_t regex;
relist *next;
};
#if DELAY_POOLS
#include "DelayConfig.h"
#endif
#if USE_ICMP
#include "icmp/IcmpConfig.h"
#endif
#include "HelperChildConfig.h"
/* forward decl for SquidConfig, see RemovalPolicy.h */
+class CpuAffinityMap;
class RemovalPolicySettings;
class external_acl;
class Store;
struct SquidConfig {
struct {
/* These should be for the Store::Root instance.
* this needs pluggable parsing to be done smoothly.
*/
int highWaterMark;
int lowWaterMark;
} Swap;
size_t memMaxSize;
struct {
int64_t min;
int pct;
int64_t max;
} quickAbort;
@@ -594,40 +595,41 @@ struct SquidConfig {
struct {
char *cert;
char *key;
int version;
char *options;
char *cipher;
char *cafile;
char *capath;
char *crlfile;
char *flags;
acl_access *cert_error;
SSL_CTX *sslContext;
} ssl_client;
#endif
char *accept_filter;
int umask;
int max_filedescriptors;
int workers;
+ CpuAffinityMap *cpuAffinityMap;
#if USE_LOADABLE_MODULES
wordlist *loadable_module_names;
#endif
int client_ip_max_connections;
};
SQUIDCEXTERN SquidConfig Config;
struct SquidConfig2 {
struct {
int enable_purge;
int mangle_request_headers;
} onoff;
uid_t effectiveUserID;
gid_t effectiveGroupID;
};
SQUIDCEXTERN SquidConfig2 Config2;
=== modified file 'src/tools.cc'
--- src/tools.cc 2010-08-09 11:06:36 +0000
+++ src/tools.cc 2010-09-17 10:46:01 +0000
@@ -807,67 +807,87 @@ no_suid(void)
#endif
}
bool
IamMasterProcess()
{
return KidIdentifier == 0;
}
bool
IamWorkerProcess()
{
// when there is only one process, it has to be the worker
if (opt_no_daemon || Config.workers == 0)
return true;
return 0 < KidIdentifier && KidIdentifier <= Config.workers;
}
bool
+InDaemonMode()
+{
+ return !opt_no_daemon && Config.workers > 0;
+}
+
+bool
UsingSmp()
{
return !opt_no_daemon && Config.workers > 1;
}
bool
IamCoordinatorProcess()
{
return UsingSmp() && KidIdentifier == Config.workers + 1;
}
bool
IamPrimaryProcess()
{
// when there is only one process, it has to be primary
if (opt_no_daemon || Config.workers == 0)
return true;
// when there is a master and worker process, the master delegates
// primary functions to its only kid
if (Config.workers == 1)
return IamWorkerProcess();
// in SMP mode, multiple kids delegate primary functions to the coordinator
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)
{
int fd;
const char *f = NULL;
mode_t old_umask;
char buf[32];
if (!IamPrimaryProcess())
return;
if ((f = Config.pidFilename) == NULL)
return;
if (!strcmp(Config.pidFilename, "none"))
return;
enter_suid();
old_umask = umask(022);