diff --git a/emper/Dispatcher.cpp b/emper/Dispatcher.cpp
index af53cebe09fa6943c1a842b73a0680a081ca8b46..44b156a051256f890bf375c8fd54aa592599e5bc 100644
--- a/emper/Dispatcher.cpp
+++ b/emper/Dispatcher.cpp
@@ -1,11 +1,21 @@
 // SPDX-License-Identifier: LGPL-3.0-or-later
-// Copyright © 2020 Florian Schmaus
+// Copyright © 2020-2022 Florian Schmaus
 #include "Dispatcher.hpp"
 
 #include <sched.h>
 
+#include <atomic>
+#include <chrono>
+
 #include "Emper.hpp"
 #include "Runtime.hpp"	// for Runtime
+#include "WakeupStrategy.hpp"
+#include "sleep_strategy/SemaphoreWorkerSleepStrategy.hpp"
+#include "stats/Worker.hpp"
+
+// Note that we initialize this with true to avoid counting workers
+// that initially go to sleep as being woken up in vain.
+thread_local bool Dispatcher::dispatchedWork = true;
 
 auto Dispatcher::getDispatchLoop() -> func_t {
 	return [this] { dispatchLoop(); };
@@ -26,14 +36,39 @@ void Dispatcher::dispatchLoopSleep() {
 	auto& workerSleepStrategy = runtime.workerSleepStrategy;
 	auto& terminateWorkers = runtime.terminateWorkers;
 
+	using clock = std::chrono::system_clock;
+	std::chrono::time_point<clock> sleepStart;
+	bool workerSlept = false;
+
 	bool canWake;
 	do {
 		// Notify the wakeup strategy about our sleep attempt
+		// TODO: We could take stats how often the wakeupStrategy decided
+		// that the worker should not sleep, and how often this was the
+		// right decission (based on dispatchedWork and a new thread_local sleepDenied).
 		if (!wakeupStrategy.canSleep()) break;
 
+		if constexpr (emper::STATS_WORKER_SLEEP) {
+			if (!workerSlept) {
+				if (!dispatchedWork) emper::stats::worker->wakeupInVainCount++;
+				workerSlept = true;
+				sleepStart = clock::now();
+			}
+		}
+
 		workerSleepStrategy.sleep();
 
 		// We always wakeup if the runtime is terminating
 		canWake = wakeupStrategy.canWakeup() || terminateWorkers.load(std::memory_order_relaxed);
 	} while (!canWake);
+
+	if constexpr (emper::STATS_WORKER_SLEEP) {
+		if (workerSlept) {
+			auto sleepStop = clock::now();
+			emper::stats::worker->recordSleep(sleepStart, sleepStop);
+			// TODO: Not sure if we should set dispatchedWork to false regardless of the value of
+			// firstSleep
+			dispatchedWork = false;
+		}
+	}
 }
diff --git a/emper/Dispatcher.hpp b/emper/Dispatcher.hpp
index 363c7732c62be89637d5bba368b508da199c325c..bccd2eaa6fe037632f7c185003dd4bd5c2fc0940 100644
--- a/emper/Dispatcher.hpp
+++ b/emper/Dispatcher.hpp
@@ -6,6 +6,7 @@
 #include "Common.hpp"
 #include "Context.hpp"
 #include "Debug.hpp"
+#include "Emper.hpp"
 #include "Fiber.hpp"
 #include "emper-common.h"
 
@@ -16,6 +17,11 @@ class Dispatcher : public Logger<LogSubsystem::DISP> {
  private:
 	void dispatchLoopSleep();
 
+	/**
+	 * Marker bool if the worker dispatched work, used for "worker sleep" stats.
+	 */
+	static thread_local bool dispatchedWork;
+
  protected:
 	Runtime& runtime;
 
@@ -30,6 +36,11 @@ class Dispatcher : public Logger<LogSubsystem::DISP> {
 	// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
 	inline void dispatch(AbstractFiber* fiber) {
 		LOGD("executing fiber " << fiber);
+
+		if constexpr (emper::STATS_WORKER_SLEEP) {
+			dispatchedWork = true;
+		}
+
 		Context::setCurrentFiber(fiber);
 		fiber->run();
 	}
diff --git a/emper/Emper.hpp b/emper/Emper.hpp
index bc87b2eaa9db06dbbd2d8ff24e5c917b7cdfc5e9..02df6edf814c1231191add6857956f1ecfda8b95 100644
--- a/emper/Emper.hpp
+++ b/emper/Emper.hpp
@@ -317,4 +317,12 @@ const bool STACK_GUARD_PAGE =
 #endif
 		;
 
+constexpr bool STATS_WORKER_SLEEP =
+#ifdef EMPER_STATS_WORKER_SLEEP
+		true
+#else
+		false
+#endif
+		;
+
 }	 // namespace emper
diff --git a/emper/Runtime.cpp b/emper/Runtime.cpp
index a897ff116a73c2c15dd36b64e146c26649cc9e23..144cd013252b31b2fd172efa6c8aff77688f3ff4 100644
--- a/emper/Runtime.cpp
+++ b/emper/Runtime.cpp
@@ -17,6 +17,7 @@
 #include <fstream>	// IWYU pragma: keep
 #include <iostream>
 #include <memory>	 // for __shared_ptr_access, shared_ptr
+#include <ratio>
 #include <string>	 // for string
 #include <thread>
 
@@ -498,6 +499,17 @@ void Runtime::printStats(std::ostream& out, bool detailed) {
 	}
 
 	globalWorkerStats.print(out);
+
+	auto now = std::chrono::system_clock::now();
+	std::chrono::duration<uint64_t, std::nano> lifetime = now - constructionTime;
+	uint64_t totalAvailableCpuTime = lifetime.count() * workerCount;
+
+	if constexpr (emper::STATS_WORKER_SLEEP) {
+		double sleepPercentage = static_cast<double>(globalWorkerStats.totalSleepNanos) /
+														 static_cast<double>(totalAvailableCpuTime);
+		out << "sleep-percentage: " << sleepPercentage << std::endl;
+	}
+
 	if (detailed) {
 		for (auto& stats : workerStatsSnapshot) {
 			stats.print();
diff --git a/emper/Runtime.hpp b/emper/Runtime.hpp
index 4fefbd5101d2c5dc2b6ebfe368650a10e68f188c..9546b932d79e1d1efc001298c5a59fd479a598df 100644
--- a/emper/Runtime.hpp
+++ b/emper/Runtime.hpp
@@ -6,6 +6,7 @@
 #include <signal.h>
 
 #include <atomic>
+#include <chrono>
 #include <cstdint>		 // for intptr_t
 #include <functional>	 // for function
 #include <iostream>
@@ -70,6 +71,9 @@ class Runtime : public Logger<LogSubsystem::RUNTI> {
 
 	using StrategyFactory = std::function<RuntimeStrategy*(Runtime&)>;
 
+	const std::chrono::time_point<std::chrono::system_clock> constructionTime =
+			std::chrono::system_clock::now();
+
  private:
 	static std::mutex currentRuntimeMutex;
 	static Runtime* currentRuntime;
diff --git a/emper/lib/env.hpp b/emper/lib/env.hpp
index 1179253da971193d0bb7da885e23ea730526b802..e5764e92f0457256bbd3d3f01426b3cbf636da76 100644
--- a/emper/lib/env.hpp
+++ b/emper/lib/env.hpp
@@ -4,7 +4,7 @@
 
 #include <cinttypes>
 #include <cstdlib>
-#include <iterator>
+#include <limits>
 #include <memory>
 #include <optional>
 #include <ostream>
diff --git a/emper/stats/Worker.cpp b/emper/stats/Worker.cpp
index f60e884300bd40704aece7c8c3e04fdded9ff48c..a984564ca09d8700100ac77830b99afee1ac90d0 100644
--- a/emper/stats/Worker.cpp
+++ b/emper/stats/Worker.cpp
@@ -3,6 +3,7 @@
 #include "Worker.hpp"
 
 #include <algorithm>
+#include <ratio>
 #include <string>
 
 #include "Emper.hpp"
@@ -51,6 +52,19 @@ void Worker::recordStackUsage(size_t stackUsage) {
 	lastStackUsage.push_back(stackUsage);
 }
 
+void Worker::recordSleep(std::chrono::time_point<std::chrono::system_clock>& sleepStart,
+												 std::chrono::time_point<std::chrono::system_clock>& sleepStop) {
+	std::chrono::duration<uint64_t, std::nano> sleepNanosDuration = sleepStop - sleepStart;
+	uint64_t sleepNanos = sleepNanosDuration.count();
+
+	totalSleepNanos += sleepNanos;
+	maxSleepNanos = std::max(maxSleepNanos, sleepNanos);
+	minSleepNanos = std::min(minSleepNanos, sleepNanos);
+	avgSleepNanos.update(sleepNanos);
+
+	sleepCount++;
+}
+
 auto Worker::operator+=(const Worker& other) -> Worker& {
 	maxBlockedContexts = std::max(maxBlockedContexts, other.maxBlockedContexts);
 	blockedGeneric += other.blockedGeneric;
@@ -72,6 +86,15 @@ auto Worker::operator+=(const Worker& other) -> Worker& {
 		lastStackUsage.clear();
 	}
 
+	if constexpr (emper::STATS_WORKER_SLEEP) {
+		sleepCount += other.sleepCount;
+		totalSleepNanos += other.totalSleepNanos;
+		maxSleepNanos = std::max(maxSleepNanos, other.maxSleepNanos);
+		minSleepNanos = std::min(minSleepNanos, other.minSleepNanos);
+		avgSleepNanos.update(static_cast<uint64_t>(other.avgSleepNanos.getAverage()));
+		wakeupInVainCount += other.wakeupInVainCount;
+	}
+
 	return *this;
 }
 
@@ -102,6 +125,18 @@ void Worker::print(std::ostream& out) {
 			out << std::endl;
 		}
 	}
+
+	if constexpr (emper::STATS_WORKER_SLEEP) {
+		// clang-format off
+		out << "sleep-count: " << sleepCount << std::endl
+				<< "wakeup-in-vain-count: " << wakeupInVainCount << std::endl
+				<< "total-sleep-ns: " << totalSleepNanos << std::endl
+				<< "max-sleep-ns: " << maxSleepNanos << std::endl
+				<< "min-sleep-ns: " << minSleepNanos << std::endl
+				<< "avg-sleep-ns: " << avgSleepNanos.getAverage() << std::endl
+			;
+		// clang-format on
+	}
 }
 
 }	 // namespace emper::stats
diff --git a/emper/stats/Worker.hpp b/emper/stats/Worker.hpp
index d7e1092a41b206406c4192504a35c97230469a65..c70dd52adf10ef9e0ef51f0af006c2f78c98fbd6 100644
--- a/emper/stats/Worker.hpp
+++ b/emper/stats/Worker.hpp
@@ -3,6 +3,7 @@
 #pragma once
 
 #include <boost/circular_buffer.hpp>
+#include <chrono>
 #include <cstddef>
 #include <cstdint>
 #include <iostream>
@@ -16,6 +17,7 @@ enum class LogSubsystem;
 template <LogSubsystem logSubsystem>
 class Blockable;	// IWYU pragma: keep
 class Context;
+class Dispatcher;
 class Runtime;
 
 namespace emper::stats {
@@ -43,6 +45,17 @@ class Worker {
 
 	void recordStackUsage(size_t stackUsage);
 
+	uint64_t sleepCount = 0;
+	uint64_t totalSleepNanos = 0;
+	uint64_t maxSleepNanos = 0;
+	uint64_t minSleepNanos = UINT64_MAX;
+	emper::lib::math::RunningAverage<double, uint64_t> avgSleepNanos;
+
+	void recordSleep(std::chrono::time_point<std::chrono::system_clock>& sleepStart,
+									 std::chrono::time_point<std::chrono::system_clock>& sleepStop);
+
+	size_t wakeupInVainCount = 0;
+
 	auto operator+=(const Worker& other) -> Worker&;
 
  public:
@@ -51,6 +64,7 @@ class Worker {
 	template <LogSubsystem>
 	friend class ::Blockable;
 	friend class ::Context;
+	friend class ::Dispatcher;
 	friend class ::Runtime;
 };
 
diff --git a/meson.build b/meson.build
index 6ef7c078f701a26f69c6b4310a70c7bbe209f3b6..9499fc8884237ddc99d50c45a36b4c60a9c4741e 100644
--- a/meson.build
+++ b/meson.build
@@ -58,12 +58,37 @@ ws_queue_scheduler = get_option('ws_queue_scheduler')
 assume_page_size = get_option('assume_page_size')
 assume_cache_line_size = get_option('assume_cache_line_size')
 
+stats = get_option('stats')
+
+stats_all = get_option('stats_all')
+stats_worker_sleep_option = get_option('stats_worker_sleep')
+if stats_worker_sleep_option == 'enabled' or (stats_all and stats_worker_sleep_option == 'auto')
+  stats_worker_sleep = true
+  stats = true
+else
+  stats_worker_sleep = false
+endif
+stats_stack_usage_option = get_option('stats_stack_usage')
+if stats_stack_usage_option == 'auto'
+  if stats_all
+    stats_stack_usage = 'efficient'
+  else
+    stats_stack_usage = 'disabled'
+  endif
+else
+  stats_stack_usage = stats_stack_usage_option
+endif
+if stats_stack_usage != 'disabled'
+  stats = true
+endif
+
+
 conf_data.set('EMPER_WORKER_SLEEP', get_option('worker_sleep'))
 conf_data.set('EMPER_WORKER_WAKEUP_STRATEGY', get_option('worker_wakeup_strategy'))
 conf_data.set('EMPER_WORKER_IGNORE_WAKEUP_HINT', get_option('worker_ignore_wakeup_hint'))
 conf_data.set('EMPER_LOCKED_MPSC_QUEUE', get_option('locked_mpsc_queue'))
 conf_data.set('EMPER_OVERFLOW_QUEUE', get_option('overflow_queue'))
-conf_data.set('EMPER_STATS', get_option('stats'))
+conf_data.set('EMPER_STATS', stats)
 conf_data.set('EMPER_OVERFLOW_QUEUE', get_option('overflow_queue'))
 conf_data.set('EMPER_BLOCKED_CONTEXT_SET', get_option('blocked_context_set'))
 conf_data.set('EMPER_SET_AFFINITY_ON_BLOCK', get_option('set_affinity_on_block'))
@@ -77,7 +102,8 @@ conf_data.set('EMPER_ASSUME_PAGE_SIZE', assume_page_size)
 conf_data.set('EMPER_MIN_CONTEXT_STACK_SIZE', get_option('min_context_stack_size'))
 conf_data.set('EMPER_ASSUME_CACHE_LINE_SIZE', assume_cache_line_size)
 conf_data.set('EMPER_STACK_GUARD_PAGE', get_option('stack_guard_page'))
-conf_data.set('EMPER_STATS_STACK_USAGE', get_option('stats_stack_usage'))
+conf_data.set('EMPER_STATS_STACK_USAGE', stats_stack_usage)
+conf_data.set('EMPER_STATS_WORKER_SLEEP', stats_worker_sleep)
 
 context_alignment = get_option('context_alignment')
 if context_alignment == 'none'
diff --git a/meson_options.txt b/meson_options.txt
index 23bd0bfeb06b3e8319d09ab0e3176f5edd32858c..71da8976fe6eff571a2b6964da754f86c543b736 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -127,11 +127,29 @@ option(
   type: 'combo',
   choices: [
     'disabled',
+    'auto',
     'efficient',
     'accurate',
   ],
-  value: 'disabled',
-  description: 'Collect stack-usage stats via stack watermarking'
+  value: 'auto',
+  description: 'Collect stack-usage stats via stack watermarking',
+)
+option(
+  'stats_worker_sleep',
+  type: 'combo',
+  choices: [
+    'disabled',
+    'auto',
+    'enabled',
+  ],
+  value: 'auto',
+  description: 'Collect stats about worker sleep',
+)
+option(
+  'stats_all',
+  type: 'boolean',
+  value: false,
+  description: 'Enable the collection of all EMPER runtime-system stats',
 )
 option(
   'default_scheduling_strategy',