WIP(taskgrinder): tool that creates/mutates/kills a bunch of tasks
Change-Id: Id172f36e6057d608df77b805f72887f209238ad1
diff --git a/system/ulib/task-utils/walker.cpp b/system/ulib/task-utils/walker.cpp
index 33ed08e..2db5ba6 100644
--- a/system/ulib/task-utils/walker.cpp
+++ b/system/ulib/task-utils/walker.cpp
@@ -115,7 +115,7 @@
zx_status_t status;
- // get the list of processes under this job
+ // get the list of threads under this process
status = fetch_children(process, process_koid, ZX_INFO_PROCESS_THREADS,
"ZX_INFO_PROCESS_THREADS", koids);
if (status != ZX_OK) {
diff --git a/system/utest/taskgrinder/rules.mk b/system/utest/taskgrinder/rules.mk
new file mode 100644
index 0000000..5838b26
--- /dev/null
+++ b/system/utest/taskgrinder/rules.mk
@@ -0,0 +1,26 @@
+# 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.
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_TYPE := userapp
+
+MODULE_SRCS += \
+ $(LOCAL_DIR)/taskgrinder.cpp
+
+MODULE_NAME := taskgrinder
+
+MODULE_LIBS := \
+ system/ulib/fdio \
+ system/ulib/zircon \
+ system/ulib/c
+
+MODULE_STATIC_LIBS := \
+ system/ulib/zx \
+ system/ulib/fbl \
+ system/ulib/zxcpp
+
+include make/module.mk
diff --git a/system/utest/taskgrinder/taskgrinder.cpp b/system/utest/taskgrinder/taskgrinder.cpp
new file mode 100644
index 0000000..5e8fca7
--- /dev/null
+++ b/system/utest/taskgrinder/taskgrinder.cpp
@@ -0,0 +1,418 @@
+// 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 <inttypes.h>
+
+#include <zircon/compiler.h>
+#include <zircon/status.h>
+#include <zircon/syscalls.h>
+#include <zircon/syscalls/object.h>
+#include <zircon/types.h>
+#include <zx/handle.h>
+#include <zx/job.h>
+#include <fbl/auto_lock.h>
+#include <fbl/mutex.h>
+
+#include "vector.h"
+
+/*
+threads creating and removing children (by closing final handles)
+thread walking children, getting handles, calling INFO on them, closing them
+
+handle to deep leaf job, none in between, let the whole thing collapse
+
+hard part is seeing if we actually hit any corner cases
+*/
+
+#define RETURN_IF_ERROR(x) \
+ do { \
+ zx_status_t TRY_status__ = (x); \
+ if (TRY_status__ != ZX_OK) { \
+ fprintf(stderr, "%s:%d: %s failed: %s (%d)\n", \
+ __func__, __LINE__, #x, \
+ zx_status_get_string(TRY_status__), TRY_status__); \
+ return TRY_status__; \
+ } \
+ } while (0)
+
+typedef Vector<zx::handle> HandleVector;
+
+bool is_good_handle(zx_handle_t h) {
+ zx_info_handle_basic_t info;
+ zx_status_t s = zx_object_get_info(h, ZX_INFO_HANDLE_BASIC,
+ &info, sizeof(info), nullptr, nullptr);
+ return s == ZX_OK;
+}
+
+void tvtest() {
+ static constexpr size_t kNumHandles = 16;
+ zx_handle_t raw_handles[kNumHandles];
+ {
+ Vector<zx::handle> handles;
+ for (size_t i = 0; i < kNumHandles; i++) {
+ zx::handle h;
+ zx_status_t s = zx_event_create(0u, h.reset_and_get_address());
+ if (s != ZX_OK) {
+ fprintf(stderr, "Can't create event %zu: %d\n", i, s);
+ return;
+ }
+ raw_handles[i] = h.get();
+ handles.push_back(fbl::move(h));
+ ZX_DEBUG_ASSERT(!h.is_valid());
+ ZX_DEBUG_ASSERT(handles[handles.size() - 1].get() == raw_handles[i]);
+ }
+
+ for (const auto& h : handles) {
+ ZX_DEBUG_ASSERT(is_good_handle(h.get()));
+ printf("Good: %" PRIu32 "\n", h.get());
+ }
+ }
+
+ for (size_t i = 0; i < kNumHandles; i++) {
+ ZX_DEBUG_ASSERT(!is_good_handle(raw_handles[i]));
+ printf("Bad: %" PRIu32 "\n", raw_handles[i]);
+ }
+ printf("*** ok ***\n");
+}
+
+// Creates child jobs until it hits the bottom, closing intermediate handles
+// along the way.
+zx_status_t create_max_height_job(zx_handle_t parent_job,
+ zx::handle* leaf_job) {
+ bool first = true;
+ zx_handle_t prev_job = parent_job;
+ while (true) {
+ zx_handle_t child_job;
+ zx_status_t s = zx_job_create(prev_job, 0u, &child_job);
+ if (s == ZX_ERR_OUT_OF_RANGE) {
+ // Hit the max job height.
+ leaf_job->reset(prev_job);
+ return ZX_OK;
+ }
+
+ if (!first) {
+ zx_handle_close(prev_job);
+ } else {
+ first = false;
+ }
+
+ if (s != ZX_OK) {
+ leaf_job->reset();
+ return s;
+ }
+ //xxx give it a unique name; supply prefix
+ zx_object_set_property(child_job, ZX_PROP_NAME, "tg-job", 7);
+ prev_job = child_job;
+ }
+}
+
+// Creates some number of jobs under a parent job, pushing their handles
+// onto the output vector.
+zx_status_t create_child_jobs(zx_handle_t parent_job, size_t n,
+ HandleVector* out_handles) {
+ for (size_t i = 0; i < n; i++) {
+ zx::handle child;
+ RETURN_IF_ERROR(zx_job_create(parent_job, 0u,
+ child.reset_and_get_address()));
+ //xxx give it a unique name; supply prefix
+ child.set_property(ZX_PROP_NAME, "tg-job", 7);
+ out_handles->push_back(fbl::move(child));
+ }
+ return ZX_OK;
+}
+
+// Creates some number of processes under a parent job, pushing their handles
+// onto the output vector.
+zx_status_t create_child_processes(zx_handle_t parent_job, size_t n,
+ HandleVector* out_handles) {
+ for (size_t i = 0; i < n; i++) {
+ zx::handle child;
+ zx::handle vmar;
+ //xxx give it a unique name; supply prefix
+ RETURN_IF_ERROR(zx_process_create(parent_job, "tg-proc", 8, 0u,
+ child.reset_and_get_address(),
+ vmar.reset_and_get_address()));
+ out_handles->push_back(fbl::move(child));
+ // Let the VMAR handle close.
+ }
+ return ZX_OK;
+}
+
+// Creates some number of threads under a parent process, pushing their handles
+// onto the output vector.
+zx_status_t create_child_threads(zx_handle_t parent_process, size_t n,
+ HandleVector* out_handles) {
+ for (size_t i = 0; i < n; i++) {
+ zx::handle child;
+ //xxx give it a unique name; supply prefix
+ RETURN_IF_ERROR(zx_thread_create(parent_process, "tg-thread", 10,
+ 0u, child.reset_and_get_address()));
+ out_handles->push_back(fbl::move(child));
+ }
+ return ZX_OK;
+}
+
+//xxx something that keeps creating children, writing handles to a pool?
+//xxx another thing that reads handles out of the pool and closes them?
+//xxx watch out for synchronization on that pool serializing things
+//xxx could have a thread grab a bunch of handles and then operate on them
+//xxx on its own
+
+//xxx child-walker function: take this process or job, walk its children;
+// maybe recurse
+
+class HandleRegistry {
+public:
+ void AddJobs(HandleVector* jobs) {
+ if (!jobs->empty()) {
+ fbl::AutoLock al(&jobs_lock_);
+ Merge(jobs, &jobs_, &num_jobs_);
+ }
+ }
+
+ void AddProcesses(HandleVector* processes) {
+ if (!processes->empty()) {
+ fbl::AutoLock al(&processes_lock_);
+ Merge(processes, &processes_, &num_processes_);
+ }
+ }
+
+ void AddThreads(HandleVector* threads) {
+ if (!threads->empty()) {
+ fbl::AutoLock al(&threads_lock_);
+ Merge(threads, &threads_, &num_threads_);
+ }
+ }
+
+ __attribute__((warn_unused_result)) zx_handle_t ReleaseRandomJob() {
+ fbl::AutoLock al(&jobs_lock_);
+ return ReleaseRandomHandle(&jobs_, &num_jobs_);
+ }
+
+ __attribute__((warn_unused_result)) zx_handle_t ReleaseRandomProcess() {
+ fbl::AutoLock al(&processes_lock_);
+ return ReleaseRandomHandle(&processes_, &num_processes_);
+ }
+
+ __attribute__((warn_unused_result)) zx_handle_t ReleaseRandomThread() {
+ fbl::AutoLock al(&threads_lock_);
+ return ReleaseRandomHandle(&threads_, &num_threads_);
+ }
+
+ __attribute__((warn_unused_result)) zx_handle_t ReleaseRandomTask() {
+ size_t total = num_jobs_ + num_processes_ + num_threads_;
+ const size_t r = rand() % total;
+ if (r < num_jobs_) {
+ return ReleaseRandomJob();
+ } else if (r < num_jobs_ + num_processes_) {
+ return ReleaseRandomProcess();
+ } else {
+ return ReleaseRandomThread();
+ }
+ //xxx try another if we picked a list with no handles
+ }
+
+ //xxx use atomics
+ size_t num_jobs() const { return num_jobs_; }
+ size_t num_processes() const { return num_processes_; }
+ size_t num_threads() const { return num_threads_; }
+ size_t num_tasks() const {
+ return num_jobs_ + num_processes_ + num_threads_;
+ }
+
+private:
+ static void Merge(HandleVector* src, HandleVector* dst, size_t* count) {
+ const size_t dst_size = dst->size(); // No holes after this index.
+ //xxx use an iterator
+ size_t di = 0; // Destination index
+ for (auto& sit : *src) {
+ if (!sit.is_valid()) {
+ continue;
+ }
+ // Look for a hole in the destination.
+ while (di < dst_size && (*dst)[di].is_valid()) {
+ di++;
+ }
+ if (di < dst_size) {
+ (*dst)[di] = fbl::move(sit);
+ } else {
+ dst->push_back(fbl::move(sit));
+ }
+ }
+ *count += src->size();
+ }
+
+ static __attribute__((warn_unused_result))
+ zx_handle_t
+ ReleaseRandomHandle(HandleVector* hv, size_t* count) {
+ const size_t size = hv->size();
+ if (size == 0) {
+ return ZX_HANDLE_INVALID;
+ }
+ const size_t start = rand() % size;
+ //xxx use an iterator
+ for (size_t i = start; i < size; i++) {
+ if ((*hv)[i].is_valid()) {
+ (*count)--;
+ return (*hv)[i].release();
+ }
+ }
+ for (size_t i = 0; i < start; i++) {
+ if ((*hv)[i].is_valid()) {
+ (*count)--;
+ return (*hv)[i].release();
+ }
+ }
+ return ZX_HANDLE_INVALID;
+ }
+
+ mutable fbl::Mutex jobs_lock_;
+ HandleVector jobs_; // TA_GUARDED(jobs_lock_);
+ size_t num_jobs_; // TA_GUARDED(jobs_lock_);
+
+ mutable fbl::Mutex processes_lock_;
+ HandleVector processes_; // TA_GUARDED(processes_lock_);
+ size_t num_processes_; // TA_GUARDED(processes_lock_);
+
+ mutable fbl::Mutex threads_lock_;
+ HandleVector threads_; // TA_GUARDED(threads_lock_);
+ size_t num_threads_; // TA_GUARDED(threads_lock_);
+};
+
+//xxx Pass in as a param
+static constexpr size_t kMaxTasks = 1000;
+
+#define MTRACE(args...) printf(args)
+
+zx_status_t mutate(HandleRegistry* registry) {
+ size_t total = registry->num_tasks();
+
+ enum {
+ OP_ADD,
+ OP_DELETE,
+ } op_class;
+
+ // Randomly pick between add, mutate, and delete.
+ if (total < kMaxTasks / 10) {
+ op_class = OP_ADD;
+ } else if (total > (9 * kMaxTasks) / 10) {
+ op_class = OP_DELETE;
+ } else {
+ op_class = (rand() % 32) < 16 ? OP_ADD : OP_DELETE;
+ }
+
+ enum {
+ TARGET_JOB,
+ TARGET_PROCESS,
+ TARGET_THREAD,
+ } op_target;
+ const size_t r = rand() % 48;
+ if (r < 16) {
+ op_target = TARGET_JOB;
+ } else if (r < 32) {
+ op_target = TARGET_PROCESS;
+ } else {
+ op_target = TARGET_THREAD;
+ }
+
+ // Handles that should go into the registry before we return.
+ HandleVector jobs;
+ HandleVector processes;
+ HandleVector threads;
+
+ switch (op_class) {
+ case OP_ADD: {
+ const size_t num_children = rand() % 5 + 1;
+ switch (op_target) {
+ case TARGET_JOB: {
+ zx_handle_t parent = registry->ReleaseRandomJob();
+ if (parent != ZX_HANDLE_INVALID) {
+ MTRACE("Create %zu jobs\n", num_children);
+ jobs.push_back(zx::handle(parent));
+ create_child_jobs(parent, num_children, &jobs);
+ //xxx if creation fails with BAD_STATE, the parent
+ //xxx is probably dead; don't put it back in the list
+ }
+ //xxx chance to create super-deep job
+ } break;
+ case TARGET_PROCESS: {
+ zx_handle_t parent = registry->ReleaseRandomJob();
+ if (parent != ZX_HANDLE_INVALID) {
+ MTRACE("Create %zu processes\n", num_children);
+ jobs.push_back(zx::handle(parent));
+ create_child_processes(parent, num_children, &processes);
+ }
+ } break;
+ case TARGET_THREAD: {
+ zx_handle_t parent = registry->ReleaseRandomProcess();
+ if (parent != ZX_HANDLE_INVALID) {
+ MTRACE("Create %zu threads\n", num_children);
+ processes.push_back(zx::handle(parent));
+ create_child_threads(parent, num_children, &threads);
+ }
+ } break;
+ }
+ } break;
+ case OP_DELETE: {
+ const bool kill = rand() % 32 < 16;
+ const bool close = rand() % 32 < 16;
+ if (kill || close) {
+ zx_handle_t task = registry->ReleaseRandomTask();
+ if (task != ZX_HANDLE_INVALID) {
+ if (kill) {
+ MTRACE("Kill one\n");
+ zx_task_kill(task);
+ }
+ if (close) {
+ MTRACE("Close one\n");
+ zx_handle_close(task);
+ } else {
+ MTRACE("(Close one)\n");
+ //xxx need to figure out the type so we can put it back.
+ zx_handle_close(task);
+ }
+ }
+ }
+ } break;
+ }
+
+ registry->AddJobs(&jobs);
+ registry->AddProcesses(&processes);
+ registry->AddThreads(&threads);
+
+ return ZX_OK;
+}
+
+zx_status_t buildup(const zx::handle& root_job) {
+ HandleRegistry registry;
+ {
+ HandleVector jobs;
+ jobs.push_back(zx::handle(root_job.get())); //xxx can't let them delete this
+ registry.AddJobs(&jobs);
+ }
+ for (int i = 0; i < 1000; i++) {
+ mutate(®istry);
+ if (i > 0 && i % 100 == 0) {
+ printf("%d mutations. Press a key:\n", i);
+ fgetc(stdin);
+ }
+ }
+ printf("Mutations done. Press a key:\n");
+ fgetc(stdin);
+
+ printf("Done.\n");
+ return ZX_OK;
+}
+
+int main(int argc, char** argv) {
+ //tvtest();
+ zx::handle test_root_job;
+ zx_status_t s = zx_job_create(zx_job_default(), 0u,
+ test_root_job.reset_and_get_address());
+ if (s != ZX_OK) {
+ return s;
+ }
+ test_root_job.set_property(ZX_PROP_NAME, "tg-root", 8);
+ return buildup(test_root_job);
+}
diff --git a/system/utest/taskgrinder/vector.h b/system/utest/taskgrinder/vector.h
new file mode 100644
index 0000000..89ca324
--- /dev/null
+++ b/system/utest/taskgrinder/vector.h
@@ -0,0 +1,55 @@
+// 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.
+
+#pragma once
+
+#include <fbl/array.h>
+#include <fbl/type_support.h>
+
+// A simple subset of std::vector.
+template <typename T>
+class Vector : private fbl::Array<T> {
+private:
+ static constexpr size_t kInitialCapacity = 8;
+ typedef fbl::Array<T> arrt;
+
+public:
+ Vector()
+ : arrt(Alloc(kInitialCapacity), kInitialCapacity) {
+ }
+
+ void push_back(T&& val) {
+ if (size() == size_) {
+ const size_t new_size = (size_ + 1) * 2;
+ // TODO(dbort): To do this properly, we'd need to avoid
+ // constructing any elements beyond size_.
+ T* a = Alloc(new_size);
+ for (size_t i = 0; i < size_; i++) {
+ a[i] = fbl::move(arrt::operator[](i));
+ }
+ arrt::reset(a, new_size);
+ }
+ arrt::operator[](size_++) = fbl::move(val);
+ }
+
+ T& operator[](size_t i) const {
+ ZX_DEBUG_ASSERT(i < size_);
+ return arrt::operator[](i);
+ }
+
+ using arrt::begin;
+
+ T* end() const { return begin() + size_; }
+
+ size_t size() const { return size_; }
+
+ bool empty() const { return size_ == 0; }
+
+private:
+ T* Alloc(size_t size) {
+ return new T[size];
+ }
+
+ size_t size_ = 0;
+};