diff --git a/benchmarks/Makefile.sources b/benchmarks/Makefile.sources
index d150035aa..b80a7644c 100644
--- a/benchmarks/Makefile.sources
+++ b/benchmarks/Makefile.sources
@@ -17,6 +17,7 @@ benchmarks_prog_list =			\
 	gem_wsim			\
 	kms_vblank			\
 	prime_lookup			\
+	sim_wsim			\
 	vgem_mmap			\
 	$(NULL)
 
diff --git a/benchmarks/sim_wsim.c b/benchmarks/sim_wsim.c
new file mode 100644
index 000000000..cb8e7adc4
--- /dev/null
+++ b/benchmarks/sim_wsim.c
@@ -0,0 +1,434 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "igt_aux.h"
+
+#if 0
+#define DBG(...) printf(__VA_ARGS__)
+#else
+#define DBG(...) do { } while(0)
+#endif
+
+struct dependency {
+	struct task *signal;
+	struct igt_list link;
+};
+
+struct task {
+	struct igt_list link;
+	struct igt_list sched;
+	struct igt_list signals;
+	int step;
+	int ctx;
+	int class;
+	int instance;
+	long min, max;
+	long duration;
+	long deadline;
+	bool visited;
+};
+
+struct work {
+	struct task **tasks;
+	unsigned int count, size;
+
+	struct igt_list ends;
+};
+
+static void add_dependency(struct task *task, struct task *signal)
+{
+	struct dependency *dep;
+
+	DBG("%s %d (context %d, engine %d) -> %d (context %d, engine %d)\n",
+	    __func__,
+	    signal->step, signal->ctx, signal->class,
+	    task->step, task->ctx, task->class);
+
+	dep = malloc(sizeof(*dep));
+	dep->signal = signal;
+	igt_list_add(&dep->link, &task->signals);
+	igt_list_del(&signal->link);
+	igt_list_init(&signal->link);
+}
+
+enum class {
+	RCS,
+	BCS,
+	VCS,
+	VECS,
+};
+
+static void add_task(struct work *work, char *line)
+{
+	static const char *engines[] = {
+		[RCS]  = "rcs",
+		[BCS]  = "bcs",
+		[VCS]  = "vcs",
+		[VECS] = "vecs",
+	};
+	struct task *task;
+	char *token;
+	int i;
+
+	task = malloc(sizeof(*task));
+
+	igt_list_init(&task->signals);
+	igt_list_init(&task->sched);
+	task->step = work->count;
+	task->visited = false;
+
+	/*
+	 * 1.RCS.2800-3100.-1.0
+	 * - context
+	 * - engine
+	 * - delay
+	 * - dependencies
+	 * - sync
+	 */
+	DBG("line='%s'\n", line);
+
+	/* context */
+	token = strtok(line, ".");
+	DBG("context='%s'\n", token);
+	task->ctx  = atoi(token);
+	if (!task->ctx)
+		return;
+
+	/* engine */
+	token = strtok(NULL, ".");
+	DBG("engine='%s'\n", token);
+	task->class = -1;
+	for (i = 0; i < sizeof(engines)/sizeof(*engines); i++) {
+		const int len = strlen(engines[i]);
+		if (!strncasecmp(token, engines[i], len)) {
+			task->class = i;
+			if (token[len])
+				task->instance = atoi(token + len);
+			else
+				task->instance = -1;
+			break;
+		}
+	}
+
+	/* delay */
+	token = strtok(NULL, ".");
+	DBG("delay='%s'\n", token);
+	task->min = strtol(token, &token, 0);
+	if (*token)
+		task->max = strtol(token + 1, NULL, 0);
+	else
+		task->max = task->min;
+	task->duration = (task->min + task->max) / 2;
+	DBG("min=%lu, max=%lu; duration=%lu\n", task->min, task->max, task->duration);
+
+	/* dependencies */
+	token = strtok(NULL, ".");
+	DBG("deps='%s'\n", token);
+	while ((i = strtol(token, &token, 0))) {
+		add_dependency(task, work->tasks[work->count + i]);
+		if (*token)
+			token++;
+	}
+
+	/* add a dependency for the context+engine timeline */
+	for (i = work->count; --i >= 0; ) {
+		if (work->tasks[i]->ctx == task->ctx &&
+		    work->tasks[i]->class == task->class) {
+			add_dependency(task, work->tasks[i]);
+			break;
+		}
+	}
+
+	igt_list_add(&task->link, &work->ends);
+	work->tasks[work->count++] = task;
+}
+
+static struct work *parse_work(FILE *file)
+{
+	struct work *work;
+	char *line = NULL;
+	size_t len = 0;
+
+	work = malloc(sizeof(*work));
+	igt_list_init(&work->ends);
+
+	work->size = 64;
+	work->count = 0;
+	work->tasks = malloc(sizeof(*work->tasks) * work->size);
+
+	while (getline(&line, &len, file) != -1) {
+		if (work->count == work->size) {
+			work->tasks = realloc(work->tasks,
+					      sizeof(*work->tasks) * work->size);
+			work->size *= 2;
+		}
+		add_task(work, line);
+	}
+
+	free(line);
+
+	DBG("%d tasks\n", work->count);
+	return work;
+}
+
+static unsigned long sum_durations(struct task *task)
+{
+	unsigned long max_duration = 0;
+	struct task *signal = NULL;
+	struct dependency *dep;
+
+	igt_list_for_each(dep, &task->signals, link) {
+		if (dep->signal->duration > max_duration) {
+			signal = dep->signal;
+			max_duration = signal->duration;
+		}
+	}
+
+	return task->duration + (signal ? sum_durations(signal) : 0);
+}
+
+static void ideal_depth(struct work *work)
+{
+	unsigned long total_duration;
+	unsigned long max_duration;
+	struct task *task;
+	int i;
+
+	/*
+	 * The ideal depth is the longest chain of dependencies as the
+	 * dependency chain requires sequential task execution. Each
+	 * chain is assumed to be run in parallel on an infinite set of
+	 * engines, so the ratelimiting step is its longest path.
+	 */
+	max_duration = 0;
+	igt_list_for_each(task, &work->ends, link) {
+		unsigned long duration = sum_durations(task);
+		if (duration > max_duration)
+			max_duration = duration;
+	}
+
+	total_duration = 0;
+	for (i = 0; i < work->count; i++)
+		total_duration += work->tasks[i]->duration;
+
+	printf("Single client\n");
+	printf("   total duration %luus; %.2f wps\n", total_duration, 1e6/total_duration);
+	printf("   ideal duration %luus; %.2f wps\n", max_duration, 1e6/max_duration);
+}
+
+struct sim_class {
+	int ninstance;
+	unsigned int instances[4];
+};
+
+static void single_client(struct work *work)
+{
+	struct sim_class sim[] = {
+		[RCS]  = { 1 },
+		[BCS]  = { 1 },
+		[VCS]  = { 2 },
+		[VECS] = { 1 },
+	}, *class;
+	IGT_LIST(sched);
+	struct task *task;
+	unsigned long max;
+	int i, j;
+
+	for (i = 0; i < work->count; i++)
+		igt_list_init(&work->tasks[i]->sched);
+
+	igt_list_for_each(task, &work->ends, link) {
+		igt_list_add_tail(&task->sched, &sched);
+	}
+
+	igt_list_for_each(task, &sched, sched) {
+		struct dependency *dep;
+
+		igt_list_for_each(dep, &task->signals, link)
+			igt_list_move_tail(&dep->signal->sched, &sched);
+	}
+
+	igt_list_for_each_reverse(task, &sched, sched) {
+		struct dependency *dep;
+		int instance;
+
+		class = &sim[task->class];
+		max = class->instances[0];
+
+		instance = task->instance;
+		if (instance < 0) {
+			instance = 0;
+			for (i = 1; i < class->ninstance; i++) {
+				if (class->instances[i] < max) {
+					max = class->instances[i];
+					instance = i;
+				}
+			}
+		}
+
+		/*
+		 * Greedy (first available), not true optimal scheduling.
+		 *
+		 * For optimal, we do have to compute the global optimal
+		 * ordering by checking every permutation...
+		 */
+		igt_list_for_each(dep, &task->signals, link) {
+			if (dep->signal->deadline > max)
+				max = dep->signal->deadline;
+		}
+
+		DBG("task %d: engine %d, instance %d; finish %lu\n",
+		    task->step, task->class, instance, max);
+
+		task->deadline = max + task->duration;
+		class->instances[instance] = task->deadline;
+	}
+
+	max = 0;
+	for (i = 0; i < sizeof(sim)/sizeof(sim[0]); i++) {
+		class = &sim[i];
+		for (j = 0; j < class->ninstance; j++) {
+			if (class->instances[j] > max)
+				max = class->instances[j];
+		}
+	}
+
+	printf("Simulated client:\n");
+	printf("   duration %luus; %.2f wps\n", max, 1e6/max);
+}
+
+static void packed_clients(struct work *work)
+{
+	struct sim_class sim[] = {
+		[RCS]  = { 1 },
+		[BCS]  = { 1 },
+		[VCS]  = { 2 },
+		[VECS] = { 1 },
+	}, *class;
+	IGT_LIST(sched);
+	struct task *task;
+	unsigned long min;
+	int i, j;
+
+	for (i = 0; i < work->count; i++)
+		igt_list_init(&work->tasks[i]->sched);
+
+	igt_list_for_each(task, &work->ends, link)
+		igt_list_add_tail(&task->sched, &sched);
+
+	igt_list_for_each(task, &sched, sched) {
+		struct dependency *dep;
+
+		igt_list_for_each(dep, &task->signals, link)
+			igt_list_move_tail(&dep->signal->sched, &sched);
+	}
+
+	/*
+	 * Compute the maximum duration required on any engine.
+	 *
+	 * With sufficient clients forcing maximum occupancy under their weight,
+	 * the ratelimiting step becomes a single engine and how many clients
+	 * it takes to fill.
+	 */
+	igt_list_for_each_reverse(task, &sched, sched) {
+		int instance;
+
+		class = &sim[task->class];
+		instance = task->instance;
+		if (instance < 0) {
+			instance = 0;
+			min = class->instances[0];
+			for (i = 1; i < class->ninstance; i++) {
+				if (class->instances[i] < min) {
+					min = class->instances[i];
+					instance = i;
+				}
+			}
+		}
+
+		class->instances[instance] += task->duration;
+	}
+
+	min = 0;
+	for (i = 0; i < sizeof(sim)/sizeof(sim[0]); i++) {
+		class = &sim[i];
+		for (j = 0; j < class->ninstance; j++) {
+			if (class->instances[j] > min)
+				min = class->instances[j];
+		}
+	}
+
+	printf("Packed clients:\n");
+	printf("   duration %luus; %.2f wps\n", min, 1e6/min);
+}
+
+static void graphviz(struct work *work)
+{
+#if 0
+	int i, j;
+
+	printf("digraph {\n");
+	printf("  rankdir=LR;\n");
+	printf("  splines=line;\n");
+	printf("\n");
+
+	for (i = 0; i < work->count; i++) {
+		struct task *task = work->tasks[i];
+
+		if (task->visited)
+			goto skip;
+
+		printf("  subgraph cluster_%d {\n", task->ctx);
+		printf("    label=\"Context %d\"\n", task->ctx);
+		for (j = i; j < work->count; j++) {
+			if (work->tasks[j]->ctx == task->ctx) {
+				printf("    task_%03d;\n", j);
+				work->tasks[j]->visited = true;
+			}
+		}
+		printf("  }\n\n");
+
+skip:
+		task->visited = false;
+	}
+
+	for (i = 0; i < work->count; i++) {
+		struct task *task = work->tasks[i];
+		struct dependency *dep;
+
+		igt_list_for_each(dep, &task->signals, link) {
+			printf("  task_%03d -> task_%03d;\n",
+			       dep->signal->step, task->step);
+		}
+	}
+
+	printf("}\n");
+#endif
+}
+
+int main(int argc, char **argv)
+{
+	int i;
+
+	for (i = 1; i < argc; i++) {
+		FILE *file = fopen(argv[i], "r");
+		struct work *work;
+
+		if (!file) {
+			perror(argv[i]);
+			return 1;
+		}
+
+		work = parse_work(file);
+		fclose(file);
+
+		graphviz(work);
+
+		ideal_depth(work);
+		single_client(work);
+		packed_clients(work);
+	}
+
+	return 0;
+}
