Skip to content

Commit

Permalink
Add new --timeout option for stress testing
Browse files Browse the repository at this point in the history
  • Loading branch information
kimwalisch committed Feb 13, 2024
1 parent 1cf7c0f commit ec32585
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 48 deletions.
3 changes: 2 additions & 1 deletion ChangeLog
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
Changes in version 11.3, 11/02/2024
Changes in version 11.3, 13/02/2024
===================================

* stressTest.cpp: New --stress-test[=MODE] command-line option.
* RiemannR.cpp: Faster Riemann R function implementation #144.
* CmdOptions.cpp: New -R && --RiemannR command line options.
* CmdOptions.cpp: New --R-inverse command line option.
* CmdOptions.cpp: Add new --timeout option for stress testing.
* main.cpp: Improve command-line option handling.

Changes in version 11.2, 08/01/2024
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,9 @@ Options:
count prime triplets: -c3 or --count=3, ...
--cpu-info Print CPU information (cache sizes).
-d, --dist=DIST Sieve the interval [START, START + DIST].
-h, --help Print this help menu.
-n, --nth-prime Find the nth prime.
primesieve 100 -n: finds the 100th prime,
primesieve 2 100 -n: finds the 2nd prime > 100.
--no-status Turn off the progressing status.
-p, --print[=NUM] Print primes or prime k-tuplets, NUM <= 6.
Print primes: -p or --print,
print twin primes: -p2 or --print=2,
Expand All @@ -108,13 +106,15 @@ Options:
By default primesieve uses a sieve size that
matches your CPU's L1 cache size (per core) or is
slightly smaller than your CPU's L2 cache size.
--stress-test[=MODE] Run stress testing. The MODE can be either
CPU (default) or RAM. Keeps on running forever.
--stress-test[=MODE] Run a stress test. The MODE can be either
CPU (default) or RAM. The default timeout is 24h.
--test Run various correctness tests (< 1 minute).
-t, --threads=NUM Set the number of threads, NUM <= CPU cores.
Default setting: use all available CPU cores.
--time Print the time elapsed in seconds.
-v, --version Print version and license information.
--timeout=SEC Set the stress test timeout in seconds. Supported
units of time suffixes: s, m, h, d or y.
30 minutes timeout: --timeout 30m
```

## Build instructions
Expand Down
19 changes: 15 additions & 4 deletions doc/primesieve.1
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
.\" Title: primesieve
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets vsnapshot <http://docbook.sf.net/>
.\" Date: 02/12/2024
.\" Date: 02/13/2024
.\" Manual: \ \&
.\" Source: \ \&
.\" Language: English
.\"
.TH "PRIMESIEVE" "1" "02/12/2024" "\ \&" "\ \&"
.TH "PRIMESIEVE" "1" "02/13/2024" "\ \&" "\ \&"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
Expand Down Expand Up @@ -127,7 +127,7 @@ Set the size of the sieve array in KiB, 16 <=
.PP
\fB\-\-stress\-test\fR[=\fIMODE\fR]
.RS 4
Run stress testing\&.
Run a stress test\&. The
\fIMODE\fR
can be either CPU (default) or RAM\&. The CPU
\fIMODE\fR
Expand All @@ -136,7 +136,11 @@ uses little memory (on average about 225 MiB per thread) and puts the highest lo
uses much more memory than the CPU
\fIMODE\fR
(each thread uses about 1\&.16 GiB), but the CPU usually won\(cqt get as hot as in the CPU
\fIMODE\fR\&. Stress testing keeps on running until either a miscalculation occurs (due to a hardware issue) or the user cancels it using Ctrl+C\&.
\fIMODE\fR\&. Stress testing keeps on running until either a miscalculation occurs (due to a hardware issue) or the timeout expires\&. The default timeout is 24 hours, the timeout can be changed using the
\fB\-\-timeout=SECS\fR
option\&. By default the stress test uses a number of threads that matches the number of CPU cores, the number of threads can be changed using the
\fB\-\-threads=NUM\fR
option\&.
.RE
.PP
\fB\-\-test\fR
Expand All @@ -156,6 +160,13 @@ Set the number of threads, 1 <=
Print the time elapsed in seconds\&.
.RE
.PP
\fB\-\-timeout\fR=\fISECS\fR
.RS 4
Set the stress test timeout in seconds\&. Units of time for seconds, minutes, hours, days and years are also supported with the suffix s, m, h, d or y\&. E\&.g\&.
\fB\-\-timeout 10m\fR
sets a timeout of 10 minutes\&. The default stress test timeout is 24 hours\&.
.RE
.PP
\fB\-v, \-\-version\fR
.RS 4
Print version and license information\&.
Expand Down
17 changes: 13 additions & 4 deletions doc/primesieve.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,16 @@ OPTIONS
size to your CPU's L1 or L2 cache size (per core).

*--stress-test*[='MODE']::
Run stress testing. 'MODE' can be either CPU (default) or RAM. The CPU
Run a stress test. The 'MODE' can be either CPU (default) or RAM. The CPU
'MODE' uses little memory (on average about 225 MiB per thread) and puts the
highest load on the CPU. The RAM 'MODE' uses much more memory than the
CPU 'MODE' (each thread uses about 1.16 GiB), but the CPU usually won't get
as hot as in the CPU 'MODE'. Stress testing keeps on running until either a
miscalculation occurs (due to a hardware issue) or the user cancels it
using Ctrl+C.
miscalculation occurs (due to a hardware issue) or the timeout expires. The
default timeout is 24 hours, the timeout can be changed using the
*--timeout=SECS* option. By default the stress test uses a number of threads
that matches the number of CPU cores, the number of threads can be changed
using the *--threads=NUM* option.

*--test*::
Run various sieving tests.
Expand All @@ -95,7 +98,13 @@ OPTIONS
prime.

*--time*::
Print the time elapsed in seconds.
Print the time elapsed in seconds.

*--timeout*='SECS'::
Set the stress test timeout in seconds. Units of time for seconds, minutes,
hours, days and years are also supported with the suffix s, m, h, d or y.
E.g. *--timeout 10m* sets a timeout of 10 minutes. The default stress test
timeout is 24 hours.

*-v, --version*::
Print version and license information.
Expand Down
24 changes: 24 additions & 0 deletions src/app/CmdOptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,28 @@ void optionStressTest(Option& opt,
opts.stressTestMode = "CPU";
}

/// Stress test timeout
void optionTimeout(Option& opt,
CmdOptions& opts)
{
std::transform(opt.val.begin(), opt.val.end(), opt.val.begin(),
[](unsigned char c){ return std::tolower(c); });

// We support the same options as stress-ng.
// https://manpages.debian.org/unstable/stress-ng/stress-ng.1.en.html
switch (opt.val.back())
{
case 's': opt.val.pop_back(); opts.timeout = opt.getValue<int64_t>(); break;
case 'm': opt.val.pop_back(); opts.timeout = opt.getValue<int64_t>() * 60; break;
case 'h': opt.val.pop_back(); opts.timeout = opt.getValue<int64_t>() * 3600; break;
case 'd': opt.val.pop_back(); opts.timeout = opt.getValue<int64_t>() * 24 * 3600; break;
case 'y': opt.val.pop_back(); opts.timeout = opt.getValue<int64_t>() * 365 * 24 * 3600; break;

// By default assume seconds like stress-ng
default: opts.timeout = opt.getValue<int64_t>();
}
}

} // namespace

CmdOptions parseOptions(int argc, char* argv[])
Expand Down Expand Up @@ -320,6 +342,7 @@ CmdOptions parseOptions(int argc, char* argv[])
{ "-t", std::make_pair(OPTION_THREADS, REQUIRED_PARAM) },
{ "--threads", std::make_pair(OPTION_THREADS, REQUIRED_PARAM) },
{ "--time", std::make_pair(OPTION_TIME, NO_PARAM) },
{ "--timeout", std::make_pair(OPTION_TIMEOUT, REQUIRED_PARAM) },
{ "-v", std::make_pair(OPTION_VERSION, NO_PARAM) },
{ "--version", std::make_pair(OPTION_VERSION, NO_PARAM) }
};
Expand All @@ -337,6 +360,7 @@ CmdOptions parseOptions(int argc, char* argv[])
case OPTION_DISTANCE: optionDistance(opt, opts); break;
case OPTION_PRINT: optionPrint(opt, opts); break;
case OPTION_STRESS_TEST: optionStressTest(opt, opts); break;
case OPTION_TIMEOUT: optionTimeout(opt, opts); break;
case OPTION_SIZE: opts.sieveSize = opt.getValue<int>(); break;
case OPTION_THREADS: opts.threads = opt.getValue<int>(); break;
case OPTION_QUIET: opts.quiet = true; break;
Expand Down
4 changes: 4 additions & 0 deletions src/app/CmdOptions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ enum OptionID
OPTION_TEST,
OPTION_THREADS,
OPTION_TIME,
OPTION_TIMEOUT,
OPTION_VERSION
};

Expand All @@ -43,6 +44,9 @@ struct CmdOptions
int flags = 0;
int sieveSize = 0;
int threads = 0;
// Stress test timeout in seconds.
// The default timeout is 24 hours (same as stress-ng).
int64_t timeout = 24 * 3600;
bool quiet = false;
bool status = true;
bool time = false;
Expand Down
7 changes: 5 additions & 2 deletions src/app/help.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,15 @@ void help(int exitCode)
" By default primesieve uses a sieve size that\n"
" matches your CPU's L1 cache size (per core) or is\n"
" slightly smaller than your CPU's L2 cache size.\n"
" --stress-test[=MODE] Run stress testing. The MODE can be either\n"
" CPU (default) or RAM. Keeps on running forever.\n"
" --stress-test[=MODE] Run a stress test. The MODE can be either\n"
" CPU (default) or RAM. The default timeout is 24h.\n"
" --test Run various correctness tests (< 1 minute).\n"
" -t, --threads=NUM Set the number of threads, NUM <= CPU cores.\n"
" Default setting: use all available CPU cores.\n"
" --time Print the time elapsed in seconds.\n"
" --timeout=SEC Set the stress test timeout in seconds. Supported\n"
" units of time suffixes: s, m, h, d or y.\n"
" 30 minutes timeout: --timeout 30m\n"
" -v, --version Print version and license information.";

std::cout << helpMenu << std::endl;
Expand Down
100 changes: 68 additions & 32 deletions src/app/stressTest.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
///
/// @file stressTest.cpp
/// @brief Run a stress test (--stress-test[=MODE] command-line
/// option) that puts maximum load on the CPU (default) or RAM
/// and verify that there are no miscalculations due to
/// hardware issues.
///
/// Stress testing keeps on running until a miscalculation
/// occurs (which shouldn't happen on most PCs) or until the
/// user cancels it using Ctrl+C.
/// option) that puts maximum load on the CPU (default) or RAM.
/// The stress test keeps on running until either a
/// miscalculation occurs (due to a hardware issue) or the
/// timeout (--timeout=SECS option) expires.
///
/// Copyright (C) 2024 Kim Walisch, <[email protected]>
///
Expand Down Expand Up @@ -117,32 +114,54 @@ const Array<uint64_t, 100> primeCounts_1e19 =
2285748648ull, 2285751228ull, 2285725270ull, 2285701010ull
};

void stressTestInfo(const std::string& stressTestMode,
std::string getTimeout(int64_t secs)
{
// Seconds per: year, day, hour, minute, second
Array<int64_t, 5> time = { 365 * 24 * 3600, 24 * 3600, 3600, 60, 1 };
Array<std::string, 5> suffix = { "y", "d", "h", "m", "s" };
std::string timeout;

for (std::size_t i = 0; i < time.size(); i++)
{
if (secs > time[i])
{
timeout += (timeout.empty()) ? "" : " ";
timeout += std::to_string(secs / time[i]) + suffix[i];
secs %= time[i];
}
}

return timeout;
}

void stressTestInfo(const CmdOptions& opts,
int threads)
{
ASSERT(stressTestMode == "CPU" || stressTestMode == "RAM");

std::cout << "Started " << stressTestMode << " stress testing using " << threads << " threads.\n";
std::cout << "Started " << opts.stressTestMode << " stress testing using " << threads << " threads.\n";
std::cout << "The expected memory usage is: " << threads << " threads * ";

if (stressTestMode == "CPU")
if (opts.stressTestMode == "CPU")
{
int threads_1e19 = threads / 5;
int threads_1e13 = threads - threads_1e19;
int avgMiB = (threads_1e13 * 3 + threads_1e19 * 1160) / threads;
double avgMiB = (threads_1e13 * 3.0 + threads_1e19 * 1160.0) / threads;
double avgGiB = avgMiB / 1024.0;

if (threads * avgMiB < 1024)
std::cout << avgMiB << " MiB = " << threads * avgMiB << " MiB.\n";
std::cout << std::fixed << std::setprecision(2) << avgMiB << " MiB = "
<< std::fixed << std::setprecision(2) << threads * avgGiB << " MiB.\n";
else
std::cout << avgMiB << " MiB = " << std::fixed
<< std::setprecision(2) << threads * avgGiB << " GiB.\n";
std::cout << std::fixed << std::setprecision(2) << avgMiB << " MiB = "
<< std::fixed << std::setprecision(2) << threads * avgGiB << " GiB.\n";
}
else // stressTestMode == "RAM"
std::cout << "1.16 GiB = " << std::fixed << std::setprecision(2) << threads * 1.16 << " GiB.\n";

std::cout << "Stress testing keeps on running until either a miscalculation occurs\n";
std::cout << "(due to a hardware issue) or you cancel it using Ctrl+C.\n";
std::cout << "The stress test keeps on running until either a miscalculation occurs\n";
std::cout << "(due to a hardware issue) or the timeout of " << getTimeout(opts.timeout) << " expires.\n";
std::cout << "You may cancel the stress test at any time using Ctrl+C.\n";
std::cout << std::endl;
}

Expand Down Expand Up @@ -246,7 +265,8 @@ void stressTest(const CmdOptions& opts)
int maxThreads = std::thread::hardware_concurrency();
int threads = (opts.threads > 0) ? opts.threads : maxThreads;
threads = inBetween(1, threads, maxThreads);
auto lastStatusOutput = std::chrono::system_clock::now();
auto timeBeginning = std::chrono::system_clock::now();
auto lastStatusOutput = timeBeginning;
int statusOutputDelay = 0;
std::mutex mutex;

Expand Down Expand Up @@ -296,31 +316,41 @@ void stressTest(const CmdOptions& opts)
}
else
{
// --timeout option
if (opts.timeout)
{
std::chrono::duration<double> secsBeginning = t2 - timeBeginning;
if (secsBeginning.count() >= (double) opts.timeout)
return;
}

// --quiet option, no status output
if (opts.quiet)
continue;

// We don't wait here. Keeping the CPU busy is more
// important then printing status output.
std::unique_lock<std::mutex> lock(mutex, std::try_to_lock);

if (lock.owns_lock())
if (!lock.owns_lock())
continue;

// We slowly increase the status output delay (in seconds)
// until it reaches 10 minutes. This way, long running
// computations don't produce excessive logs.
std::chrono::duration<double> secsStatus = t2 - lastStatusOutput;
if (secsStatus.count() >= statusOutputDelay)
{
std::chrono::duration<double> secsStatus = t2 - lastStatusOutput;

// We slowly increase the status output delay (in seconds)
// until it reaches 10 minutes. This way, long running
// computations don't produce excessive logs.
if (secsStatus.count() >= statusOutputDelay)
{
lastStatusOutput = t2;
statusOutputDelay += 5;
statusOutputDelay = std::min(statusOutputDelay, 600);
printResult(threadId, threads, i, count, secsThread, primeCounts);
}
lastStatusOutput = t2;
statusOutputDelay += 5;
statusOutputDelay = std::min(statusOutputDelay, 600);
printResult(threadId, threads, i, count, secsThread, primeCounts);
}
}
}
}
};

stressTestInfo(opts.stressTestMode, threads);
stressTestInfo(opts, threads);

using primesieve::Vector;
Vector<std::future<void>> futures;
Expand All @@ -343,4 +373,10 @@ void stressTest(const CmdOptions& opts)

for (auto& future : futures)
future.wait();

// Add new line if test results have been printed
if (statusOutputDelay > 0)
std::cout << std::endl;

std::cout << "All tests passed successfully!" << std::endl;
}

0 comments on commit ec32585

Please sign in to comment.