diff --git a/CMakeLists.txt b/CMakeLists.txt
index a962682c1b589e0aa6ff99e039089e7f043bcdca..b035f8d59070c4f84dcb64e4d2f2df4ba22b8732 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -117,6 +117,7 @@ option(LWS_WITH_RANGES "Support http ranges (RFC7233)" ON)
 option(LWS_FALLBACK_GETHOSTBYNAME "Also try to do dns resolution using gethostbyname if getaddrinfo fails" OFF)
 option(LWS_WITH_ZIP_FOPS "Support serving pre-zipped files" ON)
 option(LWS_AVOID_SIGPIPE_IGN "Android 7+ seems to need this" OFF)
+option(LWS_WITH_STATS "Keep statistics of lws internal operations" OFF)
 
 if (LWS_WITH_LWSWS)
  message(STATUS "LWS_WITH_LWSWS --> Enabling LWS_WITH_PLUGINS and LWS_WITH_LIBUV")
@@ -1767,6 +1768,7 @@ message(" LWS_PLAT_OPTEE = ${LWS_PLAT_OPTEE}")
 message(" LWS_WITH_ESP32 = ${LWS_WITH_ESP32}")
 message(" LWS_WITH_ZIP_FOPS = ${LWS_WITH_ZIP_FOPS}")
 message(" LWS_AVOID_SIGPIPE_IGN = ${LWS_AVOID_SIGPIPE_IGN}")
+message(" LWS_WITH_STATS = ${LWS_WITH_STATS}")
 
 message("---------------------------------------------------------------------")
 
diff --git a/component.mk b/component.mk
index f7dbbd8e2fc7138f500bb7bbc22d8d0252d620bb..ed334a80b548d261377b46cb00d4aabf1779d779 100644
--- a/component.mk
+++ b/component.mk
@@ -25,6 +25,7 @@ build:
 		-DCMAKE_BUILD_TYPE=RELEASE \
 		-DOPENSSL_INCLUDE_DIR=${IDF_PATH}/components/openssl/include \
 		-DOPENSSL_LIBRARIES=x \
+		-DLWS_WITH_STATS=1 \
 		-DZLIB_LIBRARY=$(BUILD_DIR_BASE)/zlib/libzlib.a \
 		-DZLIB_INCLUDE_DIR=$(COMPONENT_PATH)/../zlib \
 		-DLWS_WITH_ESP32=1 ;\
diff --git a/lib/context.c b/lib/context.c
index 435f48efe0410e3f4fc32fd72657fe1fa3ee40aa..f791ca6afa75e8e2754e3dde0ffdf229ed77d9ba 100644
--- a/lib/context.c
+++ b/lib/context.c
@@ -616,6 +616,9 @@ lws_create_context(struct lws_context_creation_info *info)
 	lwsl_info(" LWS_MAX_SMP           : %u\n", LWS_MAX_SMP);
 	lwsl_info(" SPEC_LATEST_SUPPORTED : %u\n", SPEC_LATEST_SUPPORTED);
 	lwsl_info(" sizeof (*info)        : %ld\n", (long)sizeof(*info));
+#if defined(LWS_WITH_STATS)
+	lwsl_notice(" LWS_WITH_STATS        : on\n");
+#endif
 #if LWS_POSIX
 	lwsl_info(" SYSTEM_RANDOM_FILEPATH: '%s'\n", SYSTEM_RANDOM_FILEPATH);
 #endif
@@ -1109,12 +1112,13 @@ lws_context_destroy2(struct lws_context *context)
 		vh = vh1;
 	}
 
+	lws_stats_log_dump(context);
+
 	lws_ssl_context_destroy(context);
 	lws_plat_context_late_destroy(context);
 
 	if (context->external_baggage_free_on_destroy)
 		free(context->external_baggage_free_on_destroy);
 
-
 	lws_free(context);
 }
diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c
index 98b8df9a22e0fc0b8e37ac73d3660536e6e5854a..7750429fed048034e31b2e7321fb341ace377d88 100755
--- a/lib/libwebsockets.c
+++ b/lib/libwebsockets.c
@@ -249,6 +249,7 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason)
 
 	context = wsi->context;
 	pt = &context->pt[(int)wsi->tsi];
+	lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_API_CLOSE, 1);
 
 	/* if we have children, close them first */
 	if (wsi->child_list) {
@@ -3110,3 +3111,81 @@ lws_json_dump_context(const struct lws_context *context, char *buf, int len,
 }
 
 #endif
+
+#if defined(LWS_WITH_STATS)
+
+LWS_VISIBLE LWS_EXTERN uint64_t
+lws_stats_get(struct lws_context *context, int index)
+{
+	if (index >= LWSSTATS_SIZE)
+		return 0;
+
+	return context->lws_stats[index];
+}
+
+LWS_VISIBLE LWS_EXTERN void
+lws_stats_log_dump(struct lws_context *context)
+{
+	if (!context->updated)
+		return;
+
+	context->updated = 0;
+
+	lwsl_notice("\n");
+	lwsl_notice("LWS internal statistics dump ----->\n");
+	lwsl_notice("LWSSTATS_C_CONNECTIONS:                     %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_CONNECTIONS));
+	lwsl_notice("LWSSTATS_C_API_CLOSE:                       %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_API_CLOSE));
+	lwsl_notice("LWSSTATS_C_API_READ:                        %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_API_READ));
+	lwsl_notice("LWSSTATS_C_API_LWS_WRITE:                   %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_API_LWS_WRITE));
+	lwsl_notice("LWSSTATS_C_API_WRITE:                       %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_API_WRITE));
+	lwsl_notice("LWSSTATS_C_WRITE_PARTIALS:                  %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_WRITE_PARTIALS));
+	lwsl_notice("LWSSTATS_C_WRITEABLE_CB_REQ:                %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB_REQ));
+	lwsl_notice("LWSSTATS_C_WRITEABLE_CB_EFF_REQ:            %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB_EFF_REQ));
+	lwsl_notice("LWSSTATS_C_WRITEABLE_CB:                    %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB));
+	lwsl_notice("LWSSTATS_C_SSL_CONNECTIONS_FAILED:          %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_FAILED));
+	lwsl_notice("LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED:        %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED));
+	lwsl_notice("LWSSTATS_C_TIMEOUTS:                        %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_TIMEOUTS));
+	lwsl_notice("LWSSTATS_C_SERVICE_ENTRY:                   %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SERVICE_ENTRY));
+	lwsl_notice("LWSSTATS_B_READ:                            %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_B_READ));
+	lwsl_notice("LWSSTATS_B_WRITE:                           %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_B_WRITE));
+	lwsl_notice("LWSSTATS_B_PARTIALS_ACCEPTED_PARTS:         %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_B_PARTIALS_ACCEPTED_PARTS));
+	lwsl_notice("LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY: %8llums\n", (unsigned long long)lws_stats_get(context, LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY) / 1000);
+	if (lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED))
+		lwsl_notice("  Avg accept delay:                         %8llums\n",
+			(unsigned long long)(lws_stats_get(context, LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY) /
+			lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED)) / 1000);
+	lwsl_notice("LWSSTATS_MS_WRITABLE_DELAY:                 %8lluus\n",
+			(unsigned long long)lws_stats_get(context, LWSSTATS_MS_WRITABLE_DELAY));
+	lwsl_notice("LWSSTATS_MS_WORST_WRITABLE_DELAY:           %8lluus\n",
+				(unsigned long long)lws_stats_get(context, LWSSTATS_MS_WORST_WRITABLE_DELAY));
+	if (lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB))
+		lwsl_notice("  Avg writable delay:                       %8lluus\n",
+			(unsigned long long)(lws_stats_get(context, LWSSTATS_MS_WRITABLE_DELAY) /
+			lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB)));
+	lwsl_notice("\n");
+}
+
+void
+lws_stats_atomic_bump(struct lws_context * context,
+		struct lws_context_per_thread *pt, int index, uint64_t bump)
+{
+	lws_pt_lock(pt);
+	context->lws_stats[index] += bump;
+	if (index != LWSSTATS_C_SERVICE_ENTRY)
+		context->updated = 1;
+	lws_pt_unlock(pt);
+}
+
+void
+lws_stats_atomic_max(struct lws_context * context,
+		struct lws_context_per_thread *pt, int index, uint64_t val)
+{
+	lws_pt_lock(pt);
+	if (val > context->lws_stats[index]) {
+		context->lws_stats[index] = val;
+		context->updated = 1;
+	}
+	lws_pt_unlock(pt);
+}
+
+#endif
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index 3baaf4eea0f4492a0fd7a546e4cc7b364293a96d..4065aad1c4a63c33288cdb45a1fa746deef643c7 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -4784,6 +4784,54 @@ lws_email_destroy(struct lws_email *email);
 #endif
 //@}
 
+/*
+ * Stats are all uint64_t numbers that start at 0.
+ * Index names here have the convention
+ *
+ *  _C_ counter
+ *  _B_ byte count
+ *  _MS_ millisecond count
+ */
+
+enum {
+	LWSSTATS_C_CONNECTIONS, /**< count incoming connections */
+	LWSSTATS_C_API_CLOSE, /**< count calls to close api */
+	LWSSTATS_C_API_READ, /**< count calls to read from socket api */
+	LWSSTATS_C_API_LWS_WRITE, /**< count calls to lws_write API */
+	LWSSTATS_C_API_WRITE, /**< count calls to write API */
+	LWSSTATS_C_WRITE_PARTIALS, /**< count of partial writes */
+	LWSSTATS_C_WRITEABLE_CB_REQ, /**< count of writable callback requests */
+	LWSSTATS_C_WRITEABLE_CB_EFF_REQ, /**< count of effective writable callback requests */
+	LWSSTATS_C_WRITEABLE_CB, /**< count of writable callbacks */
+	LWSSTATS_C_SSL_CONNECTIONS_FAILED, /**< count of failed SSL connections */
+	LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED, /**< count of accepted SSL connections */
+	LWSSTATS_C_TIMEOUTS, /**< count of timed-out connections */
+	LWSSTATS_C_SERVICE_ENTRY, /**< count of entries to lws service loop */
+	LWSSTATS_B_READ, /**< aggregate bytes read */
+	LWSSTATS_B_WRITE, /**< aggregate bytes written */
+	LWSSTATS_B_PARTIALS_ACCEPTED_PARTS, /**< aggreate of size of accepted write data from new partials */
+	LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY, /**< aggregate delay in accepting connection */
+	LWSSTATS_MS_WRITABLE_DELAY, /**< aggregate delay between asking for writable and getting cb */
+	LWSSTATS_MS_WORST_WRITABLE_DELAY, /**< single worst delay between asking for writable and getting cb */
+
+	/* Add new things just above here ---^
+	 * This is part of the ABI, don't needlessly break compatibility */
+	LWSSTATS_SIZE
+};
+
+#if defined(LWS_WITH_STATS)
+
+LWS_VISIBLE LWS_EXTERN uint64_t
+lws_stats_get(struct lws_context *context, int index);
+LWS_VISIBLE LWS_EXTERN void
+lws_stats_log_dump(struct lws_context *context);
+#else
+static inline uint64_t
+lws_stats_get(struct lws_context *context, int index) { return 0; }
+static inline void
+lws_stats_log_dump(struct lws_context *context) { }
+#endif
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/lws-plat-esp32.c b/lib/lws-plat-esp32.c
index 19536ce9b90e58f21570de89b53e66f9b4abef31..dc549111f8a6f25b0ab014845346ce19534c346e 100644
--- a/lib/lws-plat-esp32.c
+++ b/lib/lws-plat-esp32.c
@@ -115,6 +115,7 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
 		return 1;
 
 	pt = &context->pt[tsi];
+	lws_stats_atomic_bump(context, pt, LWSSTATS_C_SERVICE_ENTRY, 1);
 
 	if (timeout_ms < 0)
 		goto faked_service;
diff --git a/lib/lws-plat-unix.c b/lib/lws-plat-unix.c
index 3c41a4668e1aa6418318552c75b300bb6b4a720d..b9f317bb78fa6a3f9104849ff55edce859a947f0 100644
--- a/lib/lws-plat-unix.c
+++ b/lib/lws-plat-unix.c
@@ -114,6 +114,8 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
 
 	pt = &context->pt[tsi];
 
+	lws_stats_atomic_bump(context, pt, LWSSTATS_C_SERVICE_ENTRY, 1);
+
 	if (timeout_ms < 0)
 		goto faked_service;
 
diff --git a/lib/output.c b/lib/output.c
index a41b0e0dfc68f5fd77ce843c9dafbca5bab7a5c2..efb26068fb10c9a6805a8c763af6bbabdcc18ded 100644
--- a/lib/output.c
+++ b/lib/output.c
@@ -95,10 +95,13 @@ LWS_VISIBLE void lwsl_hexdump(void *vbuf, size_t len)
 int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len)
 {
 	struct lws_context *context = lws_get_context(wsi);
+	struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
 	size_t real_len = len;
 	unsigned int n;
 	int m;
 
+	lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_API_WRITE, 1);
+
 	if (!len)
 		return 0;
 	/* just ignore sends after we cleared the truncation buffer */
@@ -207,6 +210,9 @@ handle_truncated_send:
 	lwsl_debug("%p new partial sent %d from %lu total\n", wsi, n,
 		    (unsigned long)real_len);
 
+	lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITE_PARTIALS, 1);
+	lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_B_PARTIALS_ACCEPTED_PARTS, n);
+
 	/*
 	 *  - if we still have a suitable malloc lying around, use it
 	 *  - or, if too small, reallocate it
@@ -244,12 +250,16 @@ LWS_VISIBLE int lws_write(struct lws *wsi, unsigned char *buf, size_t len,
 	int pre = 0, n;
 	size_t orig_len = len;
 
+	lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_API_LWS_WRITE, 1);
+
 	if ((int)len < 0) {
 		lwsl_err("%s: suspicious len int %d, ulong %lu\n", __func__,
 				(int)len, (unsigned long)len);
 		return -1;
 	}
 
+	lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_B_WRITE, len);
+
 #ifdef LWS_WITH_ACCESS_LOG
 	wsi->access_log.sent += len;
 #endif
@@ -766,12 +776,17 @@ file_had_it:
 LWS_VISIBLE int
 lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, int len)
 {
+	struct lws_context *context = wsi->context;
+	struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
 	int n;
 
+	lws_stats_atomic_bump(context, pt, LWSSTATS_C_API_READ, 1);
+
 	n = recv(wsi->desc.sockfd, (char *)buf, len, 0);
 	if (n >= 0) {
 		if (wsi->vhost)
 			wsi->vhost->conn_stats.rx += n;
+		lws_stats_atomic_bump(context, pt, LWSSTATS_B_READ, n);
 		lws_restart_ws_ping_pong_timer(wsi);
 		return n;
 	}
diff --git a/lib/pollfd.c b/lib/pollfd.c
index b43f79134ad6b16e0a9708d298734a1077add518..7c3a0efa436b6aff3ef937c0971a6eec6ab0329d 100644
--- a/lib/pollfd.c
+++ b/lib/pollfd.c
@@ -336,6 +336,7 @@ lws_change_pollfd(struct lws *wsi, int _and, int _or)
 LWS_VISIBLE int
 lws_callback_on_writable(struct lws *wsi)
 {
+	struct lws_context_per_thread *pt;
 #ifdef LWS_USE_HTTP2
 	struct lws *network_wsi, *wsi2;
 	int already;
@@ -347,6 +348,15 @@ lws_callback_on_writable(struct lws *wsi)
 	if (wsi->socket_is_permanently_unusable)
 		return 0;
 
+	pt = &wsi->context->pt[(int)wsi->tsi];
+	lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITEABLE_CB_REQ, 1);
+#if defined(LWS_WITH_STATS)
+	if (!wsi->active_writable_req_us) {
+		wsi->active_writable_req_us = time_in_microseconds();
+		lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITEABLE_CB_EFF_REQ, 1);
+	}
+#endif
+
 #ifdef LWS_USE_HTTP2
 	lwsl_info("%s: %p\n", __func__, wsi);
 
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index d59bd7b3c9f1fb548aba6ed692ade6b92bcf92eb..21fccdc703487fc104918a30d7745dd36205045d 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -879,6 +879,12 @@ struct lws_context {
 	char worst_latency_info[256];
 #endif
 
+#if defined(LWS_WITH_STATS)
+	uint64_t lws_stats[LWSSTATS_SIZE];
+	uint64_t last_dump;
+	int updated;
+#endif
+
 	int max_fds;
 #if defined(LWS_USE_LIBEV) || defined(LWS_USE_LIBUV) || defined(LWS_USE_LIBEVENT)
 	int use_ev_sigint;
@@ -1465,6 +1471,9 @@ struct lws {
 	SSL *ssl;
 	BIO *client_bio;
 	struct lws *pending_read_list_prev, *pending_read_list_next;
+#if defined(LWS_WITH_STATS)
+	uint64_t accept_start_us;
+#endif
 #endif
 #ifdef LWS_WITH_HTTP_PROXY
 	struct lws_rewrite *rw;
@@ -1474,7 +1483,9 @@ struct lws {
 	unsigned long latency_start;
 #endif
 	lws_sock_file_fd_type desc; /* .filefd / .sockfd */
-
+#if defined(LWS_WITH_STATS)
+	uint64_t active_writable_req_us;
+#endif
 	/* ints */
 	int position_in_fds_table;
 	int rxflow_len;
@@ -2072,6 +2083,22 @@ lws_same_vh_protocol_remove(struct lws *wsi);
 LWS_EXTERN void
 lws_same_vh_protocol_insert(struct lws *wsi, int n);
 
+#if defined(LWS_WITH_STATS)
+void
+lws_stats_atomic_bump(struct lws_context * context,
+		struct lws_context_per_thread *pt, int index, uint64_t bump);
+void
+lws_stats_atomic_max(struct lws_context * context,
+		struct lws_context_per_thread *pt, int index, uint64_t val);
+#else
+static inline uint64_t lws_stats_atomic_bump(struct lws_context * context,
+		struct lws_context_per_thread *pt, int index, uint64_t bump) {
+	(void)context; (void)pt; (void)index; (void)bump; return 0; }
+static inline uint64_t lws_stats_atomic_max(struct lws_context * context,
+		struct lws_context_per_thread *pt, int index, uint64_t val) {
+	(void)context; (void)pt; (void)index; (void)val; return 0; }
+#endif
+
 #ifdef __cplusplus
 };
 #endif
diff --git a/lib/server.c b/lib/server.c
index d1ff1e9ffe5ab9ad35ac9cb54951d0edea9b0e1e..f442b94a0dad6622e69169da39e2e4b6c3ebe513 100644
--- a/lib/server.c
+++ b/lib/server.c
@@ -1757,6 +1757,7 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type,
 {
 	struct lws_context *context = vh->context;
 	struct lws *new_wsi = lws_create_new_server_wsi(vh);
+	struct lws_context_per_thread *pt;
 	int n, ssl = 0;
 
 	if (!new_wsi) {
@@ -1764,6 +1765,8 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type,
 			compatible_close(fd.sockfd);
 		return NULL;
 	}
+	pt = &context->pt[(int)new_wsi->tsi];
+	lws_stats_atomic_bump(context, pt, LWSSTATS_C_CONNECTIONS, 1);
 
 	if (parent) {
 		new_wsi->parent = parent;
@@ -2192,6 +2195,16 @@ try_pollout:
 		}
 
 		if (wsi->mode == LWSCM_RAW) {
+			lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITEABLE_CB, 1);
+#if defined(LWS_WITH_STATS)
+			{
+				uint64_t ul = time_in_microseconds() - wsi->active_writable_req_us;
+
+				lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_MS_WRITABLE_DELAY, ul);
+				lws_stats_atomic_max(wsi->context, pt, LWSSTATS_MS_WORST_WRITABLE_DELAY, ul);
+				wsi->active_writable_req_us = 0;
+			}
+#endif
 			n = user_callback_handle_rxflow(wsi->protocol->callback,
 					wsi, LWS_CALLBACK_RAW_WRITEABLE,
 					wsi->user_space, NULL, 0);
@@ -2206,6 +2219,18 @@ try_pollout:
 			break;
 
 		if (wsi->state != LWSS_HTTP_ISSUING_FILE) {
+
+			lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITEABLE_CB, 1);
+#if defined(LWS_WITH_STATS)
+			{
+				uint64_t ul = time_in_microseconds() - wsi->active_writable_req_us;
+
+				lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_MS_WRITABLE_DELAY, ul);
+				lws_stats_atomic_max(wsi->context, pt, LWSSTATS_MS_WORST_WRITABLE_DELAY, ul);
+				wsi->active_writable_req_us = 0;
+			}
+#endif
+
 			n = user_callback_handle_rxflow(wsi->protocol->callback,
 					wsi, LWS_CALLBACK_HTTP_WRITEABLE,
 					wsi->user_space, NULL, 0);
diff --git a/lib/service.c b/lib/service.c
index b23defd765b9226000532a6abf8c799311c7e594..e33c6a7c814eaf7cdcb43b0253529812e0d4db24 100644
--- a/lib/service.c
+++ b/lib/service.c
@@ -24,8 +24,20 @@
 static int
 lws_calllback_as_writeable(struct lws *wsi)
 {
+	struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
 	int n;
 
+	lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITEABLE_CB, 1);
+#if defined(LWS_WITH_STATS)
+	{
+		uint64_t ul = time_in_microseconds() - wsi->active_writable_req_us;
+
+		lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_MS_WRITABLE_DELAY, ul);
+		lws_stats_atomic_max(wsi->context, pt, LWSSTATS_MS_WORST_WRITABLE_DELAY, ul);
+		wsi->active_writable_req_us = 0;
+	}
+#endif
+
 	switch (wsi->mode) {
 	case LWSCM_RAW:
 		n = LWS_CALLBACK_RAW_WRITEABLE;
@@ -425,6 +437,8 @@ lws_service_timeout_check(struct lws *wsi, unsigned int sec)
 		if (wsi->desc.sockfd != LWS_SOCK_INVALID && wsi->position_in_fds_table >= 0)
 			n = pt->fds[wsi->position_in_fds_table].events;
 
+		lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_TIMEOUTS, 1);
+
 		/* no need to log normal idle keepalive timeout */
 		if (wsi->pending_timeout != PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE)
 			lwsl_notice("wsi %p: TIMEDOUT WAITING on %d (did hdr %d, ah %p, wl %d, pfd events %d) %llu vs %llu\n",
@@ -786,6 +800,13 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t
 	if (context->last_timeout_check_s != now) {
 		context->last_timeout_check_s = now;
 
+#if defined(LWS_WITH_STATS)
+		if (!tsi && now - context->last_dump > 10) {
+			lws_stats_log_dump(context);
+			context->last_dump = now;
+		}
+#endif
+
 		lws_plat_service_periodic(context);
 
 		/* retire unused deprecated context */
diff --git a/lib/ssl.c b/lib/ssl.c
index a7969f3d03fd19bb5b18739c128a936f825ea28e..b5f9e1e64b2cb80c4fa96f391be451147e5bdfa4 100644
--- a/lib/ssl.c
+++ b/lib/ssl.c
@@ -298,6 +298,8 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, int len)
 	if (!wsi->ssl)
 		return lws_ssl_capable_read_no_ssl(wsi, buf, len);
 
+	lws_stats_atomic_bump(context, pt, LWSSTATS_C_API_READ, 1);
+
 	errno = 0;
 	n = SSL_read(wsi->ssl, buf, len);
 #if defined(LWS_WITH_ESP32)
@@ -354,6 +356,8 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, int len)
 		return LWS_SSL_CAPABLE_ERROR;
 	}
 
+	lws_stats_atomic_bump(context, pt, LWSSTATS_B_READ, n);
+
 	if (wsi->vhost)
 		wsi->vhost->conn_stats.rx += n;
 
@@ -652,6 +656,11 @@ lws_server_socket_service_ssl(struct lws *wsi, lws_sockfd_type accept_fd)
 
 		/* normal SSL connection processing path */
 
+#if defined(LWS_WITH_STATS)
+		if (!wsi->accept_start_us)
+			wsi->accept_start_us = time_in_microseconds();
+#endif
+
 		n = SSL_accept(wsi->ssl);
 		lws_latency(context, wsi,
 			"SSL_accept LWSCM_SSL_ACK_PENDING\n", n, n == 1);
@@ -686,13 +695,18 @@ go_again:
 
 			break;
 		}
-
+		lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_SSL_CONNECTIONS_FAILED, 1);
                 lwsl_err("SSL_accept failed socket %u: %s\n", wsi->desc.sockfd,
                          lws_ssl_get_error_string(m, n, buf, sizeof(buf)));
 		lws_ssl_elaborate_error();
 		goto fail;
 
 accepted:
+		lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED, 1);
+#if defined(LWS_WITH_STATS)
+		lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY, time_in_microseconds() - wsi->accept_start_us);
+#endif
+
 		/* OK, we are accepted... give him some time to negotiate */
 		lws_set_timeout(wsi, PENDING_TIMEOUT_ESTABLISH_WITH_SERVER,
 				context->timeout_secs);
diff --git a/lws_config.h.in b/lws_config.h.in
index d83211bf87521845603b2ef36609d3d16b542e0a..96c170535a14c8578d947caccb2b6cbead5cf02e 100644
--- a/lws_config.h.in
+++ b/lws_config.h.in
@@ -133,6 +133,8 @@
 
 #cmakedefine LWS_FALLBACK_GETHOSTBYNAME
 
+#cmakedefine LWS_WITH_STATS
+
 /* OpenSSL various APIs */
 
 #cmakedefine LWS_HAVE_TLS_CLIENT_METHOD