Comparar commits

...

1 Commits

Autor SHA1 Mensagem Data
Dave Bort e16819e0dc WIP(taskgrinder): tool that creates/mutates/kills a bunch of tasks
Change-Id: Id172f36e6057d608df77b805f72887f209238ad1
2017-08-08 19:19:46 -07:00
4 arquivos alterados com 500 adições e 1 exclusões
+1 -1
Ver Arquivo
@@ -115,7 +115,7 @@ static mx_status_t do_threads_worker(
mx_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, MX_INFO_PROCESS_THREADS,
"MX_INFO_PROCESS_THREADS", koids);
if (status != MX_OK) {
+26
Ver Arquivo
@@ -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/mxio \
system/ulib/magenta \
system/ulib/c
MODULE_STATIC_LIBS := \
system/ulib/mx \
system/ulib/mxtl \
system/ulib/mxcpp
include make/module.mk
+418
Ver Arquivo
@@ -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 <magenta/compiler.h>
#include <magenta/status.h>
#include <magenta/syscalls.h>
#include <magenta/syscalls/object.h>
#include <magenta/types.h>
#include <mx/handle.h>
#include <mx/job.h>
#include <mxtl/auto_lock.h>
#include <mxtl/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 { \
mx_status_t TRY_status__ = (x); \
if (TRY_status__ != MX_OK) { \
fprintf(stderr, "%s:%d: %s failed: %s (%d)\n", \
__func__, __LINE__, #x, \
mx_status_get_string(TRY_status__), TRY_status__); \
return TRY_status__; \
} \
} while (0)
typedef Vector<mx::handle> HandleVector;
bool is_good_handle(mx_handle_t h) {
mx_info_handle_basic_t info;
mx_status_t s = mx_object_get_info(h, MX_INFO_HANDLE_BASIC,
&info, sizeof(info), nullptr, nullptr);
return s == MX_OK;
}
void tvtest() {
static constexpr size_t kNumHandles = 16;
mx_handle_t raw_handles[kNumHandles];
{
Vector<mx::handle> handles;
for (size_t i = 0; i < kNumHandles; i++) {
mx::handle h;
mx_status_t s = mx_event_create(0u, h.reset_and_get_address());
if (s != MX_OK) {
fprintf(stderr, "Can't create event %zu: %d\n", i, s);
return;
}
raw_handles[i] = h.get();
handles.push_back(mxtl::move(h));
MX_DEBUG_ASSERT(!h.is_valid());
MX_DEBUG_ASSERT(handles[handles.size() - 1].get() == raw_handles[i]);
}
for (const auto& h : handles) {
MX_DEBUG_ASSERT(is_good_handle(h.get()));
printf("Good: %" PRIu32 "\n", h.get());
}
}
for (size_t i = 0; i < kNumHandles; i++) {
MX_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.
mx_status_t create_max_height_job(mx_handle_t parent_job,
mx::handle* leaf_job) {
bool first = true;
mx_handle_t prev_job = parent_job;
while (true) {
mx_handle_t child_job;
mx_status_t s = mx_job_create(prev_job, 0u, &child_job);
if (s == MX_ERR_OUT_OF_RANGE) {
// Hit the max job height.
leaf_job->reset(prev_job);
return MX_OK;
}
if (!first) {
mx_handle_close(prev_job);
} else {
first = false;
}
if (s != MX_OK) {
leaf_job->reset();
return s;
}
//xxx give it a unique name; supply prefix
mx_object_set_property(child_job, MX_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.
mx_status_t create_child_jobs(mx_handle_t parent_job, size_t n,
HandleVector* out_handles) {
for (size_t i = 0; i < n; i++) {
mx::handle child;
RETURN_IF_ERROR(mx_job_create(parent_job, 0u,
child.reset_and_get_address()));
//xxx give it a unique name; supply prefix
child.set_property(MX_PROP_NAME, "tg-job", 7);
out_handles->push_back(mxtl::move(child));
}
return MX_OK;
}
// Creates some number of processes under a parent job, pushing their handles
// onto the output vector.
mx_status_t create_child_processes(mx_handle_t parent_job, size_t n,
HandleVector* out_handles) {
for (size_t i = 0; i < n; i++) {
mx::handle child;
mx::handle vmar;
//xxx give it a unique name; supply prefix
RETURN_IF_ERROR(mx_process_create(parent_job, "tg-proc", 8, 0u,
child.reset_and_get_address(),
vmar.reset_and_get_address()));
out_handles->push_back(mxtl::move(child));
// Let the VMAR handle close.
}
return MX_OK;
}
// Creates some number of threads under a parent process, pushing their handles
// onto the output vector.
mx_status_t create_child_threads(mx_handle_t parent_process, size_t n,
HandleVector* out_handles) {
for (size_t i = 0; i < n; i++) {
mx::handle child;
//xxx give it a unique name; supply prefix
RETURN_IF_ERROR(mx_thread_create(parent_process, "tg-thread", 10,
0u, child.reset_and_get_address()));
out_handles->push_back(mxtl::move(child));
}
return MX_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()) {
mxtl::AutoLock al(&jobs_lock_);
Merge(jobs, &jobs_, &num_jobs_);
}
}
void AddProcesses(HandleVector* processes) {
if (!processes->empty()) {
mxtl::AutoLock al(&processes_lock_);
Merge(processes, &processes_, &num_processes_);
}
}
void AddThreads(HandleVector* threads) {
if (!threads->empty()) {
mxtl::AutoLock al(&threads_lock_);
Merge(threads, &threads_, &num_threads_);
}
}
__attribute__((warn_unused_result)) mx_handle_t ReleaseRandomJob() {
mxtl::AutoLock al(&jobs_lock_);
return ReleaseRandomHandle(&jobs_, &num_jobs_);
}
__attribute__((warn_unused_result)) mx_handle_t ReleaseRandomProcess() {
mxtl::AutoLock al(&processes_lock_);
return ReleaseRandomHandle(&processes_, &num_processes_);
}
__attribute__((warn_unused_result)) mx_handle_t ReleaseRandomThread() {
mxtl::AutoLock al(&threads_lock_);
return ReleaseRandomHandle(&threads_, &num_threads_);
}
__attribute__((warn_unused_result)) mx_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] = mxtl::move(sit);
} else {
dst->push_back(mxtl::move(sit));
}
}
*count += src->size();
}
static __attribute__((warn_unused_result))
mx_handle_t
ReleaseRandomHandle(HandleVector* hv, size_t* count) {
const size_t size = hv->size();
if (size == 0) {
return MX_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 MX_HANDLE_INVALID;
}
mutable mxtl::Mutex jobs_lock_;
HandleVector jobs_; // TA_GUARDED(jobs_lock_);
size_t num_jobs_; // TA_GUARDED(jobs_lock_);
mutable mxtl::Mutex processes_lock_;
HandleVector processes_; // TA_GUARDED(processes_lock_);
size_t num_processes_; // TA_GUARDED(processes_lock_);
mutable mxtl::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)
mx_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: {
mx_handle_t parent = registry->ReleaseRandomJob();
if (parent != MX_HANDLE_INVALID) {
MTRACE("Create %zu jobs\n", num_children);
jobs.push_back(mx::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: {
mx_handle_t parent = registry->ReleaseRandomJob();
if (parent != MX_HANDLE_INVALID) {
MTRACE("Create %zu processes\n", num_children);
jobs.push_back(mx::handle(parent));
create_child_processes(parent, num_children, &processes);
}
} break;
case TARGET_THREAD: {
mx_handle_t parent = registry->ReleaseRandomProcess();
if (parent != MX_HANDLE_INVALID) {
MTRACE("Create %zu threads\n", num_children);
processes.push_back(mx::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) {
mx_handle_t task = registry->ReleaseRandomTask();
if (task != MX_HANDLE_INVALID) {
if (kill) {
MTRACE("Kill one\n");
mx_task_kill(task);
}
if (close) {
MTRACE("Close one\n");
mx_handle_close(task);
} else {
MTRACE("(Close one)\n");
//xxx need to figure out the type so we can put it back.
mx_handle_close(task);
}
}
}
} break;
}
registry->AddJobs(&jobs);
registry->AddProcesses(&processes);
registry->AddThreads(&threads);
return MX_OK;
}
mx_status_t buildup(const mx::handle& root_job) {
HandleRegistry registry;
{
HandleVector jobs;
jobs.push_back(mx::handle(root_job.get())); //xxx can't let them delete this
registry.AddJobs(&jobs);
}
for (int i = 0; i < 1000; i++) {
mutate(&registry);
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 MX_OK;
}
int main(int argc, char** argv) {
//tvtest();
mx::handle test_root_job;
mx_status_t s = mx_job_create(mx_job_default(), 0u,
test_root_job.reset_and_get_address());
if (s != MX_OK) {
return s;
}
test_root_job.set_property(MX_PROP_NAME, "tg-root", 8);
return buildup(test_root_job);
}
+55
Ver Arquivo
@@ -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 <mxtl/array.h>
#include <mxtl/type_support.h>
// A simple subset of std::vector.
template <typename T>
class Vector : private mxtl::Array<T> {
private:
static constexpr size_t kInitialCapacity = 8;
typedef mxtl::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] = mxtl::move(arrt::operator[](i));
}
arrt::reset(a, new_size);
}
arrt::operator[](size_++) = mxtl::move(val);
}
T& operator[](size_t i) const {
MX_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;
};