summaryrefslogtreecommitdiffstats
path: root/web/server/web_client_cache.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--web/server/web_client_cache.c148
1 files changed, 148 insertions, 0 deletions
diff --git a/web/server/web_client_cache.c b/web/server/web_client_cache.c
new file mode 100644
index 00000000..364fc76b
--- /dev/null
+++ b/web/server/web_client_cache.c
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#define WEB_SERVER_INTERNALS 1
+#include "web_client_cache.h"
+
+// ----------------------------------------------------------------------------
+// allocate and free web_clients
+
+// ----------------------------------------------------------------------------
+// web clients caching
+
+// When clients connect and disconnect, avoid allocating and releasing memory.
+// Instead, when new clients get connected, reuse any memory previously allocated
+// for serving web clients that are now disconnected.
+
+// The size of the cache is adaptive. It caches the structures of 2x
+// the number of currently connected clients.
+
+static struct clients_cache {
+ struct {
+ SPINLOCK spinlock;
+ struct web_client *head; // the structures of the currently connected clients
+ size_t count; // the count the currently connected clients
+
+ size_t allocated; // the number of allocations
+ size_t reused; // the number of re-uses
+ } used;
+
+ struct {
+ SPINLOCK spinlock;
+ struct web_client *head; // the cached structures, available for future clients
+ size_t count; // the number of cached structures
+ } avail;
+} web_clients_cache = {
+ .used = {
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+ .head = NULL,
+ .count = 0,
+ .reused = 0,
+ .allocated = 0,
+ },
+ .avail = {
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+ .head = NULL,
+ .count = 0,
+ },
+};
+
+// destroy the cache and free all the memory it uses
+void web_client_cache_destroy(void) {
+ internal_error(true, "web_client_cache has %zu used and %zu available clients, allocated %zu, reused %zu (hit %zu%%)."
+ , web_clients_cache.used.count
+ , web_clients_cache.avail.count
+ , web_clients_cache.used.allocated
+ , web_clients_cache.used.reused
+ , (web_clients_cache.used.allocated + web_clients_cache.used.reused)?(web_clients_cache.used.reused * 100 / (web_clients_cache.used.allocated + web_clients_cache.used.reused)):0
+ );
+
+ struct web_client *w, *t;
+
+ spinlock_lock(&web_clients_cache.avail.spinlock);
+ w = web_clients_cache.avail.head;
+ while(w) {
+ t = w;
+ w = w->cache.next;
+ web_client_free(t);
+ }
+ web_clients_cache.avail.head = NULL;
+ web_clients_cache.avail.count = 0;
+ spinlock_unlock(&web_clients_cache.avail.spinlock);
+
+// DO NOT FREE THEM IF THEY ARE USED
+// spinlock_lock(&web_clients_cache.used.spinlock);
+// w = web_clients_cache.used.head;
+// while(w) {
+// t = w;
+// w = w->next;
+// web_client_free(t);
+// }
+// web_clients_cache.used.head = NULL;
+// web_clients_cache.used.count = 0;
+// web_clients_cache.used.reused = 0;
+// web_clients_cache.used.allocated = 0;
+// spinlock_unlock(&web_clients_cache.used.spinlock);
+}
+
+struct web_client *web_client_get_from_cache(void) {
+ spinlock_lock(&web_clients_cache.avail.spinlock);
+ struct web_client *w = web_clients_cache.avail.head;
+ if(w) {
+ // get it from avail
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(web_clients_cache.avail.head, w, cache.prev, cache.next);
+ web_clients_cache.avail.count--;
+
+ spinlock_unlock(&web_clients_cache.avail.spinlock);
+ web_client_reuse_from_cache(w);
+ spinlock_lock(&web_clients_cache.used.spinlock);
+
+ web_clients_cache.used.reused++;
+ }
+ else {
+ spinlock_unlock(&web_clients_cache.avail.spinlock);
+ w = web_client_create(&netdata_buffers_statistics.buffers_web);
+ spinlock_lock(&web_clients_cache.used.spinlock);
+
+ w->id = global_statistics_web_client_connected();
+ web_clients_cache.used.allocated++;
+ }
+
+ // link it to used web clients
+ DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(web_clients_cache.used.head, w, cache.prev, cache.next);
+ web_clients_cache.used.count++;
+ spinlock_unlock(&web_clients_cache.used.spinlock);
+
+ // initialize it
+ w->use_count++;
+ w->mode = WEB_CLIENT_MODE_GET;
+ memset(w->transaction, 0, sizeof(w->transaction));
+
+ return w;
+}
+
+void web_client_release_to_cache(struct web_client *w) {
+
+#ifdef ENABLE_HTTPS
+ netdata_ssl_close(&w->ssl);
+#endif
+
+ // unlink it from the used
+ spinlock_lock(&web_clients_cache.used.spinlock);
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(web_clients_cache.used.head, w, cache.prev, cache.next);
+ ssize_t used_count = (ssize_t)--web_clients_cache.used.count;
+ spinlock_unlock(&web_clients_cache.used.spinlock);
+
+ spinlock_lock(&web_clients_cache.avail.spinlock);
+ if(w->use_count > 100 || (used_count > 0 && web_clients_cache.avail.count >= 2 * (size_t)used_count) || (used_count <= 10 && web_clients_cache.avail.count >= 20)) {
+ spinlock_unlock(&web_clients_cache.avail.spinlock);
+
+ // we have too many of them - free it
+ web_client_free(w);
+ }
+ else {
+ // link it to the avail
+ DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(web_clients_cache.avail.head, w, cache.prev, cache.next);
+ web_clients_cache.avail.count++;
+ spinlock_unlock(&web_clients_cache.avail.spinlock);
+ }
+}