GMX_MPI_TEST(4);
ThreadAffinityTestHelper helper;
helper.setLogicalProcessorCount(4);
+ helper.expectPinningMessage(false, 1);
helper.expectAffinitySet(gmx_node_rank());
helper.setAffinity(1);
}
helper.setAffinityOption(threadaffON);
helper.setOffsetAndStride(1, 2);
helper.setLogicalProcessorCount(8);
+ helper.expectWarningMatchingRegex("Applying core pinning offset 1");
+ helper.expectPinningMessage(true, 2);
helper.expectAffinitySet(1 + 2*gmx_node_rank());
helper.setAffinity(1);
}
ThreadAffinityTestHelper helper;
helper.setPhysicalNodeId(gmx_node_rank()/2);
helper.setLogicalProcessorCount(2);
+ helper.expectPinningMessage(false, 1);
helper.expectAffinitySet(gmx_node_rank()%2);
helper.setAffinity(1);
}
GMX_MPI_TEST(4);
ThreadAffinityTestHelper helper;
helper.setLogicalProcessorCount(6);
+ helper.expectWarningMatchingRegex("Oversubscribing the CPU");
+ helper.expectGenericFailureMessage();
helper.setAffinity(2);
}
ThreadAffinityTestHelper helper;
helper.setAffinityOption(threadaffON);
helper.setLogicalProcessorCount(6);
+ helper.expectWarningMatchingRegex("Oversubscribing the CPU");
+ helper.expectGenericFailureMessage();
helper.setAffinity(2);
}
public:
int currentNode() const { return gmx_node_rank() / 2; }
int indexInNode() const { return gmx_node_rank() % 2; }
+ bool isMaster() const { return gmx_node_rank() == 0; }
void setupNodes(ThreadAffinityTestHelper *helper, std::array<int, 2> cores)
{
ThreadAffinityTestHelper helper;
helper.setAffinityOption(threadaffON);
setupNodes(&helper, {{2, 1}});
+ helper.expectWarningMatchingRegexIf("Oversubscribing the CPU", isMaster() || currentNode() == 1);
+ helper.expectGenericFailureMessageIf(isMaster() || currentNode() == 1);
+ if (currentNode() == 0)
+ {
+ helper.expectPinningMessage(false, 1);
+ }
expectNodeAffinitySet(&helper, 0, indexInNode());
helper.setAffinity(1);
}
ThreadAffinityTestHelper helper;
helper.setAffinityOption(threadaffON);
setupNodes(&helper, {{1, 2}});
+ helper.expectWarningMatchingRegexIf("Oversubscribing the CPU", currentNode() == 0);
+ helper.expectGenericFailureMessageIf(currentNode() == 0);
+ if (currentNode() == 1)
+ {
+ helper.expectPinningMessage(false, 1);
+ }
expectNodeAffinitySet(&helper, 1, indexInNode());
helper.setAffinity(1);
}
ThreadAffinityTestHelper helper;
helper.setAffinityOption(threadaffON);
setupNodes(&helper, {{2, 0}});
+ helper.expectWarningMatchingRegexIf("No information on available cores", isMaster() || currentNode() == 1);
+ helper.expectGenericFailureMessageIf(isMaster() || currentNode() == 1);
+ if (currentNode() == 0)
+ {
+ helper.expectPinningMessage(false, 1);
+ }
expectNodeAffinitySet(&helper, 0, indexInNode());
helper.setAffinity(1);
}
GMX_MPI_TEST(4);
ThreadAffinityTestHelper helper;
setupNodes(&helper, {{2, 1}});
+ helper.expectWarningMatchingRegexIf("Oversubscribing the CPU", isMaster() || currentNode() == 1);
+ helper.expectGenericFailureMessageIf(isMaster() || currentNode() == 1);
+ if (currentNode() == 0)
+ {
+ helper.expectPinningMessage(false, 1);
+ }
expectNodeAffinitySet(&helper, 0, indexInNode());
helper.setAffinity(1);
}
GMX_MPI_TEST(4);
ThreadAffinityTestHelper helper;
setupNodes(&helper, {{1, 2}});
+ helper.expectWarningMatchingRegexIf("Oversubscribing the CPU", currentNode() == 0);
+ helper.expectGenericFailureMessageIf(currentNode() == 0);
+ if (currentNode() == 1)
+ {
+ helper.expectPinningMessage(false, 1);
+ }
expectNodeAffinitySet(&helper, 1, indexInNode());
helper.setAffinity(1);
}
helper.setAffinityOption(threadaffON);
helper.setOffsetAndStride(2, 0);
setupNodes(&helper, {{4, 2}});
+ helper.expectWarningMatchingRegex("Applying core pinning offset 2");
+ helper.expectWarningMatchingRegexIf("Requested offset too large", isMaster() || currentNode() == 1);
+ helper.expectGenericFailureMessageIf(isMaster() || currentNode() == 1);
+ if (currentNode() == 0)
+ {
+ helper.expectPinningMessage(false, 1);
+ }
expectNodeAffinitySet(&helper, 0, indexInNode()+2);
helper.setAffinity(1);
}
helper.setAffinityOption(threadaffON);
helper.setOffsetAndStride(0, 2);
setupNodes(&helper, {{4, 2}});
+ helper.expectWarningMatchingRegexIf("Requested stride too large", isMaster() || currentNode() == 1);
+ helper.expectGenericFailureMessageIf(isMaster() || currentNode() == 1);
+ if (currentNode() == 0)
+ {
+ helper.expectPinningMessage(true, 2);
+ }
expectNodeAffinitySet(&helper, 0, 2*indexInNode());
helper.setAffinity(1);
}
/*
* This file is part of the GROMACS molecular simulation package.
*
- * Copyright (c) 2016, by the GROMACS development team, led by
+ * Copyright (c) 2016,2017, by the GROMACS development team, led by
* Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
* and including many others, as listed in the AUTHORS file in the
* top-level source directory and at http://www.gromacs.org.
class ThreadAffinityTest : public ::testing::Test
{
public:
+
gmx::test::ThreadAffinityTestHelper helper_;
};
TEST_F(ThreadAffinityTest, DoesNothingWithAutoAndTooFewThreads)
{
helper_.setLogicalProcessorCount(4);
+ helper_.expectWarningMatchingRegex("The number of threads is not equal to the number of");
+ helper_.expectGenericFailureMessage();
helper_.setAffinity(2);
}
TEST_F(ThreadAffinityTest, DoesNothingWithAutoAndTooManyThreads)
{
helper_.setLogicalProcessorCount(4);
+ helper_.expectWarningMatchingRegex("Oversubscribing the CPU");
+ helper_.expectGenericFailureMessage();
helper_.setAffinity(8);
}
{
helper_.setAffinityOption(threadaffON);
helper_.setLogicalProcessorCount(0);
+ helper_.expectWarningMatchingRegex("No information on available cores");
+ helper_.expectGenericFailureMessage();
helper_.setAffinity(2);
}
{
helper_.setAffinityOption(threadaffON);
helper_.setLogicalProcessorCount(4);
+ helper_.expectWarningMatchingRegex("Oversubscribing the CPU");
+ helper_.expectGenericFailureMessage();
helper_.setAffinity(8);
}
helper_.setAffinityOption(threadaffON);
helper_.setOffsetAndStride(2, 0);
helper_.setLogicalProcessorCount(4);
+ helper_.expectWarningMatchingRegex("Applying core pinning offset 2");
+ helper_.expectWarningMatchingRegex("Requested offset too large");
+ helper_.expectGenericFailureMessage();
helper_.setAffinity(3);
}
helper_.setAffinityOption(threadaffON);
helper_.setOffsetAndStride(0, 2);
helper_.setLogicalProcessorCount(4);
+ helper_.expectWarningMatchingRegex("Requested stride too large");
+ helper_.expectGenericFailureMessage();
helper_.setAffinity(3);
}
{
helper_.setLogicalProcessorCount(1);
helper_.expectAffinitySet(0);
+ helper_.expectPinningMessage(false, 1);
helper_.setAffinity(1);
}
{
helper_.setAffinityOption(threadaffON);
helper_.setLogicalProcessorCount(2);
+ helper_.expectPinningMessage(false, 2);
helper_.expectAffinitySet(0);
helper_.setAffinity(1);
}
helper_.setAffinityOption(threadaffON);
helper_.setOffsetAndStride(2, 0);
helper_.setLogicalProcessorCount(4);
+ helper_.expectWarningMatchingRegex("Applying core pinning offset 2");
+ helper_.expectPinningMessage(false, 2);
helper_.expectAffinitySet(2);
helper_.setAffinity(1);
}
TEST_F(ThreadAffinityTest, HandlesPinningFailureWithSingleThread)
{
helper_.setLogicalProcessorCount(1);
+ helper_.expectPinningMessage(false, 1);
+ helper_.expectGenericFailureMessage();
helper_.expectAffinitySetThatFails(0);
helper_.setAffinity(1);
}
TEST_F(ThreadAffinityTest, PinsMultipleThreadsWithAuto)
{
helper_.setLogicalProcessorCount(2);
+ helper_.expectPinningMessage(false, 1);
helper_.expectAffinitySet({0, 1});
helper_.setAffinity(2);
}
helper_.setAffinityOption(threadaffON);
helper_.setOffsetAndStride(0, 2);
helper_.setLogicalProcessorCount(4);
+ helper_.expectPinningMessage(true, 2);
helper_.expectAffinitySet({0, 2});
helper_.setAffinity(2);
}
{
helper_.setAffinityOption(threadaffON);
helper_.setLogicalProcessorCount(2);
+ helper_.expectPinningMessage(false, 1);
+ helper_.expectGenericFailureMessage();
helper_.expectAffinitySet(0);
helper_.expectAffinitySetThatFails(1);
helper_.setAffinity(2);
#include "gromacs/hardware/hw_info.h"
#include "gromacs/mdrunutility/threadaffinity.h"
#include "gromacs/utility/logger.h"
+#include "gromacs/utility/stringutil.h"
+
+#include "testutils/loggertest.h"
struct t_commrec;
.WillOnce(Return(false));
}
+ void expectWarningMatchingRegex(const char *re)
+ {
+ expectWarningMatchingRegexIf(re, true);
+ }
+ void expectWarningMatchingRegexIf(const char *re, bool condition)
+ {
+ expectLogMessageMatchingRegexIf(MDLogger::LogLevel::Warning, re, condition);
+ }
+ void expectInfoMatchingRegex(const char *re)
+ {
+ expectInfoMatchingRegexIf(re, true);
+ }
+ void expectInfoMatchingRegexIf(const char *re, bool condition)
+ {
+ expectLogMessageMatchingRegexIf(MDLogger::LogLevel::Info, re, condition);
+ }
+ void expectGenericFailureMessage()
+ {
+ expectGenericFailureMessageIf(true);
+ }
+ void expectGenericFailureMessageIf(bool condition)
+ {
+ expectWarningMatchingRegexIf("NOTE: Thread affinity setting failed.", condition);
+ }
+ void expectPinningMessage(bool userSpecifiedStride, int stride)
+ {
+ std::string pattern = formatString("Pinning threads .* %s.* stride of %d",
+ userSpecifiedStride ? "user" : "auto",
+ stride);
+ expectInfoMatchingRegex(pattern.c_str());
+ }
+ void expectLogMessageMatchingRegexIf(MDLogger::LogLevel level,
+ const char *re, bool condition)
+ {
+ if (condition)
+ {
+ logHelper_.expectEntryMatchingRegex(level, re);
+ }
+ }
+
void setAffinity(int nthread_local)
{
if (hwTop_ == nullptr)
{
setLogicalProcessorCount(1);
}
- MDLogger mdlog;
- gmx_set_thread_affinity(nullptr, mdlog, cr_, hwOpt_, *hwTop_,
+ gmx_set_thread_affinity(logHelper_.logger(), cr_, hwOpt_, *hwTop_,
nthread_local, &affinityAccess_);
}
gmx_hw_opt_t *hwOpt_;
std::unique_ptr<HardwareTopology> hwTop_;
MockThreadAffinityAccess affinityAccess_;
+ LoggerTestHelper logHelper_;
};
} // namespace test
}
static bool
-get_thread_affinity_layout(FILE *fplog, const gmx::MDLogger &mdlog,
+get_thread_affinity_layout(const gmx::MDLogger &mdlog,
const t_commrec *cr,
const gmx::HardwareTopology &hwTop,
int threads,
}
validLayout = validLayout && !invalidValue;
- if (validLayout && fplog != nullptr)
+ if (validLayout)
{
- fprintf(fplog, "Pinning threads with a%s logical core stride of %d\n",
+ GMX_LOG(mdlog.info).appendTextFormatted(
+ "Pinning threads with a%s logical core stride of %d",
bPickPinStride ? "n auto-selected" : " user-specified",
*pin_stride);
}
nthread_local > 1 ? "s" : "");
}
+ // TODO: This output should also go through mdlog.
fprintf(stderr, "NOTE: %sAffinity setting %sfailed.\n", sbuf1, sbuf2);
}
return allAffinitiesSet;
if only PME is using threads.
*/
void
-gmx_set_thread_affinity(FILE *fplog,
- const gmx::MDLogger &mdlog,
+gmx_set_thread_affinity(const gmx::MDLogger &mdlog,
const t_commrec *cr,
const gmx_hw_opt_t *hw_opt,
const gmx::HardwareTopology &hwTop,
bool automatic = (hw_opt->thread_affinity == threadaffAUTO);
bool validLayout
- = get_thread_affinity_layout(fplog, mdlog, cr, hwTop, nthread_node, automatic,
+ = get_thread_affinity_layout(mdlog, cr, hwTop, nthread_node, automatic,
offset, &core_pinning_stride, &localityOrder);
const gmx::sfree_guard localityOrderGuard(localityOrder);
* Sets the thread affinity using the requested setting stored in hw_opt.
*/
void
-gmx_set_thread_affinity(FILE *fplog,
- const gmx::MDLogger &mdlog,
+gmx_set_thread_affinity(const gmx::MDLogger &mdlog,
const t_commrec *cr,
const gmx_hw_opt_t *hw_opt,
const gmx::HardwareTopology &hwTop,
}
/* Set the CPU affinity */
- gmx_set_thread_affinity(fplog, mdlog, cr, hw_opt, *hwinfo->hardwareTopology,
+ gmx_set_thread_affinity(mdlog, cr, hw_opt, *hwinfo->hardwareTopology,
nthread_local, nullptr);
}
#
# This file is part of the GROMACS molecular simulation package.
#
-# Copyright (c) 2011,2012,2013,2014,2015,2016, by the GROMACS development team, led by
+# Copyright (c) 2011,2012,2013,2014,2015,2016,2017, by the GROMACS development team, led by
# Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
# and including many others, as listed in the AUTHORS file in the
# top-level source directory and at http://www.gromacs.org.
cmdlinetest.cpp
integrationtests.cpp
interactivetest.cpp
+ loggertest.cpp
mpi-printer.cpp
mpitest.cpp
refdata.cpp
--- /dev/null
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2016,2017, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief
+ * Implements gmx::test::LoggerTestHelper.
+ *
+ * \author Teemu Murtola <teemu.murtola@gmail.com>
+ * \ingroup module_testutils
+ */
+#include "gmxpre.h"
+
+#include "loggertest.h"
+
+#include <gmock/gmock.h>
+
+#include "gromacs/utility/basedefinitions.h"
+#include "gromacs/utility/logger.h"
+
+namespace gmx
+{
+namespace test
+{
+
+using ::testing::NiceMock;
+
+namespace
+{
+class MockLogTarget : public ILogTarget
+{
+ public:
+ MOCK_METHOD1(writeEntry, void(const LogEntry &));
+};
+} // namespace
+
+/********************************************************************
+ * LoggerTestHelper::Impl
+ */
+
+class LoggerTestHelper::Impl
+{
+ public:
+ Impl()
+ {
+ // TODO: Add support for -stdout for echoing the log to stdout.
+ logger_.warning = LogLevelHelper(&getTarget(MDLogger::LogLevel::Warning));
+ logger_.info = LogLevelHelper(&getTarget(MDLogger::LogLevel::Info));
+ }
+
+ NiceMock<MockLogTarget> &getTarget(MDLogger::LogLevel level)
+ {
+ return targets_[static_cast<int>(level)];
+ }
+
+ NiceMock<MockLogTarget> targets_[MDLogger::LogLevelCount];
+ MDLogger logger_;
+};
+
+/********************************************************************
+ * LoggerTestHelper
+ */
+
+LoggerTestHelper::LoggerTestHelper()
+ : impl_(new Impl)
+{
+}
+
+LoggerTestHelper::~LoggerTestHelper()
+{
+}
+
+const MDLogger &LoggerTestHelper::logger()
+{
+ return impl_->logger_;
+}
+
+void LoggerTestHelper::expectEntryMatchingRegex(gmx::MDLogger::LogLevel level,
+ const char *re)
+{
+ using ::testing::ContainsRegex;
+ using ::testing::Field;
+ auto &target = impl_->getTarget(level);
+ EXPECT_CALL(target, writeEntry(Field(&LogEntry::text, ContainsRegex(re))));
+}
+
+} // namespace test
+} // namespace gmx
--- /dev/null
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2016,2017, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \libinternal \file
+ * \brief
+ * Declares gmx::test::LoggerTestHelper.
+ *
+ * \author Teemu Murtola <teemu.murtola@gmail.com>
+ * \inlibraryapi
+ * \ingroup module_testutils
+ */
+#ifndef GMX_TESTUTILS_LOGGERTEST_H
+#define GMX_TESTUTILS_LOGGERTEST_H
+
+#include "gromacs/utility/classhelpers.h"
+#include "gromacs/utility/logger.h"
+
+namespace gmx
+{
+
+namespace test
+{
+
+/*! \libinternal \brief
+ * Helper class for tests to check output written to a logger.
+ *
+ * \inlibraryapi
+ * \ingroup module_testutils
+ */
+class LoggerTestHelper
+{
+ public:
+ LoggerTestHelper();
+ ~LoggerTestHelper();
+
+ //! Returns the logger to pass to code under test.
+ const MDLogger &logger();
+
+ /*! \brief
+ * Expects a log entry at a given level matching a given regex.
+ *
+ * Currently, the order of the entries is not checked, and if this
+ * method is called once for a log level, then it needs to be called
+ * for all entries produced by the test.
+ *
+ * If not called for a log level, all entries for that level are
+ * accepted.
+ */
+ void expectEntryMatchingRegex(gmx::MDLogger::LogLevel level,
+ const char *re);
+
+ private:
+ class Impl;
+
+ PrivateImplPointer<Impl> impl_;
+};
+
+} // namespace test
+} // namespace gmx
+
+#endif