blob: 9882934f5f9e1edb2ed7f2ed0b5a98089c3d1303 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/fdio/io.h>
#include <lib/fit/defer.h>
#include <stdio.h>
#include <threads.h>
#include <unistd.h>
#include <zircon/errors.h>
#include <zircon/syscalls.h>
#include <zircon/time.h>
#include <zircon/types.h>
#include <algorithm>
#include <limits>
#include <utility>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <fbl/intrusive_single_list.h>
static constexpr uint32_t kDefaultNumThreads = 4;
static constexpr float kDefaultMinWorkMsec = 5.0f;
static constexpr float kDefaultMaxWorkMsec = 15.0f;
static constexpr float kDefaultMinSleepMsec = 1.0f;
static constexpr float kDefaultMaxSleepMsec = 2.5f;
class LoadGeneratorThread : public fbl::SinglyLinkedListable<std::unique_ptr<LoadGeneratorThread>> {
public:
LoadGeneratorThread(unsigned int seed) : seed_(seed) {}
~LoadGeneratorThread();
zx_status_t Start();
static float& min_work_msec() { return min_work_msec_; }
static float& max_work_msec() { return max_work_msec_; }
static float& min_sleep_msec() { return min_sleep_msec_; }
static float& max_sleep_msec() { return max_sleep_msec_; }
private:
int Run();
double MakeRandomDouble(double min, double max);
static float min_work_msec_;
static float max_work_msec_;
static float min_sleep_msec_;
static float max_sleep_msec_;
static volatile bool quit_;
unsigned int seed_;
bool thread_started_;
thrd_t thread_;
volatile double accumulator_;
};
float LoadGeneratorThread::min_work_msec_ = kDefaultMinWorkMsec;
float LoadGeneratorThread::max_work_msec_ = kDefaultMaxWorkMsec;
float LoadGeneratorThread::min_sleep_msec_ = kDefaultMinSleepMsec;
float LoadGeneratorThread::max_sleep_msec_ = kDefaultMaxSleepMsec;
volatile bool LoadGeneratorThread::quit_ = false;
LoadGeneratorThread::~LoadGeneratorThread() {
if (thread_started_) {
int musl_ret;
quit_ = true;
thrd_join(thread_, &musl_ret);
}
}
zx_status_t LoadGeneratorThread::Start() {
if (thread_started_)
return ZX_ERR_BAD_STATE;
int c11_res = thrd_create(
&thread_, [](void* ctx) -> int { return static_cast<LoadGeneratorThread*>(ctx)->Run(); },
this);
if (c11_res != thrd_success) {
printf("Failed to create new client thread (res %d)!\n", c11_res);
// TODO(johngro) : translate musl error
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
int LoadGeneratorThread::Run() {
constexpr double kMinNum = 1.0;
constexpr double kMaxNum = 100000000.0;
uint32_t ticks_per_msec = static_cast<uint32_t>(zx_ticks_per_second() / 1000);
accumulator_ = MakeRandomDouble(kMinNum, kMaxNum);
// While it is not time to quit, waste time performing pointless double
// precision floating point math.
while (!quit_) {
double work_delay = MakeRandomDouble(min_work_msec(), max_work_msec());
zx_ticks_t work_deadline_ticks =
zx_ticks_get() + static_cast<zx_ticks_t>(work_delay * ticks_per_msec);
while (!quit_ && (zx_ticks_get() < work_deadline_ticks)) {
accumulator_ = accumulator_ + MakeRandomDouble(kMinNum, kMaxNum);
accumulator_ = accumulator_ * MakeRandomDouble(kMinNum, kMaxNum);
accumulator_ = accumulator_ - MakeRandomDouble(kMinNum, kMaxNum);
accumulator_ = accumulator_ / MakeRandomDouble(kMinNum, kMaxNum);
double tmp = accumulator_;
accumulator_ = std::clamp<double>(tmp, 0.0, kMaxNum);
}
if (quit_)
break;
double sleep_delay = MakeRandomDouble(min_sleep_msec(), max_sleep_msec());
zx_time_t sleep_deadline =
zx_deadline_after(static_cast<zx_duration_t>(sleep_delay * 1000000.0));
do {
static constexpr zx_duration_t max_sleep = ZX_MSEC(10);
zx_time_t now = zx_clock_get_monotonic();
if (now >= sleep_deadline)
break;
if (zx_time_sub_time(sleep_deadline, now) > max_sleep) {
zx_nanosleep(zx_time_add_duration(now, max_sleep));
} else {
zx_nanosleep(sleep_deadline);
break;
}
} while (!quit_);
}
return 0;
}
double LoadGeneratorThread::MakeRandomDouble(double min, double max) {
double norm(rand_r(&seed_));
norm /= std::numeric_limits<int>::max();
return min + (norm * (max - min));
}
void usage(const char* program_name) {
printf(
"usage: %s [N] [min_work max_work] [min_sleep max_sleep] [seed]\n"
" All arguments are positional and optional.\n"
" N : Number of threads to create. Default %u\n"
" min/max_work : Min/max msec for threads to work for. Default %.1f,%.1f mSec\n"
" min/max_sleep : Min/max msec for threads to sleep for. Default %.1f,%.1f mSec\n"
" seed : RNG seed to use. Defaults to seeding from zx_clock_get\n",
program_name, kDefaultNumThreads, kDefaultMinWorkMsec, kDefaultMaxWorkMsec,
kDefaultMinSleepMsec, kDefaultMaxSleepMsec);
}
int main(int argc, char** argv) {
auto show_usage = fit::defer([argv]() { usage(argv[0]); });
// 0, 1, 3, 5 and 6 arguments are the only legal number of args.
switch (argc) {
case 1:
case 2:
case 4:
case 6:
case 7:
break;
default:
return -1;
}
// Parse and sanity check number of threads, if present.
uint32_t num_threads = kDefaultNumThreads;
if (argc >= 2) {
if (sscanf(argv[1], "%u", &num_threads) != 1)
return -1;
if (num_threads == 0)
return -1;
}
// Parse and sanity check min/max work times, if present.
if (argc >= 4) {
if (sscanf(argv[2], "%f", &LoadGeneratorThread::min_work_msec()) != 1)
return -1;
if (sscanf(argv[3], "%f", &LoadGeneratorThread::max_work_msec()) != 1)
return -1;
if (LoadGeneratorThread::min_work_msec() <= 0.0f)
return -1;
if (LoadGeneratorThread::min_work_msec() > LoadGeneratorThread::max_work_msec())
return -1;
}
// Parse and sanity check min/max sleep times, if present.
if (argc >= 6) {
if (sscanf(argv[4], "%f", &LoadGeneratorThread::min_sleep_msec()) != 1)
return -1;
if (sscanf(argv[5], "%f", &LoadGeneratorThread::max_sleep_msec()) != 1)
return -1;
if (LoadGeneratorThread::min_sleep_msec() <= 0.0f)
return -1;
if (LoadGeneratorThread::min_sleep_msec() > LoadGeneratorThread::max_sleep_msec())
return -1;
}
// Parse the PRNG seed, if present.
unsigned int seed = static_cast<unsigned int>(zx_clock_get_monotonic());
if (argc >= 7) {
if (sscanf(argv[6], "%u", &seed) != 1)
return -1;
}
// Argument parsing checks out, cancel the showing of the usage message.
show_usage.cancel();
printf(
"Creating %u load generation thread%s.\n"
"Work times : [%.3f, %.3f] mSec\n"
"Sleep times : [%.3f, %.3f] mSec\n"
"Seed : %u\n",
num_threads, num_threads == 1 ? "" : "s", LoadGeneratorThread::min_work_msec(),
LoadGeneratorThread::max_work_msec(), LoadGeneratorThread::min_sleep_msec(),
LoadGeneratorThread::max_sleep_msec(), seed);
fbl::SinglyLinkedList<std::unique_ptr<LoadGeneratorThread>> threads;
for (uint32_t i = 0; i < num_threads; ++i) {
fbl::AllocChecker ac;
std::unique_ptr<LoadGeneratorThread> t(new (&ac) LoadGeneratorThread(rand_r(&seed)));
if (!ac.check()) {
printf("Failed to create thread %u/%u\n", i + 1, num_threads);
return -1;
}
threads.push_front(std::move(t));
}
for (auto& t : threads) {
zx_status_t res = t.Start();
if (res != ZX_OK) {
printf("Failed to start thread. (res %d)\n", res);
return res;
}
}
printf("Running. Press any key to exit\n");
uint32_t event = 0;
bool state = false;
printf("-");
while (::fdio_wait_fd(STDIN_FILENO, FDIO_EVT_READABLE, &event, ZX_MSEC(500)) ==
ZX_ERR_TIMED_OUT) {
if ((state = !state)) {
printf("\b-");
} else {
printf("\b|");
}
}
printf("Shutting down...\n");
threads.clear();
printf("Finished\n");
return 0;
}