| // 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_{false}; |
| 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; |
| } |