diff --git a/src/networking.c b/src/networking.c index 85c640e34..654fda517 100644 --- a/src/networking.c +++ b/src/networking.c @@ -157,6 +157,8 @@ client *createClient(connection *conn) { c->client_list_node = NULL; c->client_tracking_redirection = 0; c->client_tracking_prefixes = NULL; + c->client_cron_last_memory_usage = 0; + c->client_cron_last_memory_type = CLIENT_TYPE_NORMAL; c->auth_callback = NULL; c->auth_callback_privdata = NULL; c->auth_module = NULL; @@ -1160,6 +1162,11 @@ void freeClient(client *c) { listDelNode(server.clients_to_close,ln); } + /* Remove the contribution that this client gave to our + * incrementally computed memory usage. */ + server.stat_clients_type_memory[c->client_cron_last_memory_type] -= + c->client_cron_last_memory_usage; + /* Release other dynamically allocated client structure fields, * and finally release the client structure itself. */ if (c->name) decrRefCount(c->name); diff --git a/src/object.c b/src/object.c index 11e335afc..52d5b11f5 100644 --- a/src/object.c +++ b/src/object.c @@ -974,30 +974,15 @@ struct redisMemOverhead *getMemoryOverheadData(void) { mh->repl_backlog = mem; mem_total += mem; - mem = 0; - if (listLength(server.clients)) { - listIter li; - listNode *ln; - size_t mem_normal = 0, mem_slaves = 0; - - listRewind(server.clients,&li); - while((ln = listNext(&li))) { - size_t mem_curr = 0; - client *c = listNodeValue(ln); - int type = getClientType(c); - mem_curr += getClientOutputBufferMemoryUsage(c); - mem_curr += sdsAllocSize(c->querybuf); - mem_curr += sizeof(client); - if (type == CLIENT_TYPE_SLAVE) - mem_slaves += mem_curr; - else - mem_normal += mem_curr; - } - mh->clients_slaves = mem_slaves; - mh->clients_normal = mem_normal; - mem = mem_slaves + mem_normal; - } - mem_total+=mem; + /* Computing the memory used by the clients would be O(N) if done + * here online. We use our values computed incrementally by + * clientsCronTrackClientsMemUsage(). */ + mh->clients_slaves = server.stat_clients_type_memory[CLIENT_TYPE_SLAVE]; + mh->clients_normal = server.stat_clients_type_memory[CLIENT_TYPE_MASTER]+ + server.stat_clients_type_memory[CLIENT_TYPE_PUBSUB]+ + server.stat_clients_type_memory[CLIENT_TYPE_NORMAL]; + mem_total += mh->clients_slaves; + mem_total += mh->clients_normal; mem = 0; if (server.aof_state != AOF_OFF) { diff --git a/src/server.c b/src/server.c index 56feb09a3..996e0f5d2 100644 --- a/src/server.c +++ b/src/server.c @@ -1593,6 +1593,28 @@ int clientsCronTrackExpansiveClients(client *c) { return 0; /* This function never terminates the client. */ } +/* Iterating all the clients in getMemoryOverheadData() is too slow and + * in turn would make the INFO command too slow. So we perform this + * computation incrementally and track the (not instantaneous but updated + * to the second) total memory used by clients using clinetsCron() in + * a more incremental way (depending on server.hz). */ +int clientsCronTrackClientsMemUsage(client *c) { + size_t mem = 0; + int type = getClientType(c); + mem += getClientOutputBufferMemoryUsage(c); + mem += sdsAllocSize(c->querybuf); + mem += sizeof(client); + /* Now that we have the memory used by the client, remove the old + * value from the old categoty, and add it back. */ + server.stat_clients_type_memory[c->client_cron_last_memory_type] -= + c->client_cron_last_memory_usage; + server.stat_clients_type_memory[type] += mem; + /* Remember what we added and where, to remove it next time. */ + c->client_cron_last_memory_usage = mem; + c->client_cron_last_memory_type = type; + return 0; +} + /* Return the max samples in the memory usage of clients tracked by * the function clientsCronTrackExpansiveClients(). */ void getExpansiveClientsInfo(size_t *in_usage, size_t *out_usage) { @@ -1653,6 +1675,7 @@ void clientsCron(void) { if (clientsCronHandleTimeout(c,now)) continue; if (clientsCronResizeQueryBuffer(c)) continue; if (clientsCronTrackExpansiveClients(c)) continue; + if (clientsCronTrackClientsMemUsage(c)) continue; } } @@ -2792,6 +2815,8 @@ void initServer(void) { server.stat_rdb_cow_bytes = 0; server.stat_aof_cow_bytes = 0; server.stat_module_cow_bytes = 0; + for (int j = 0; j < CLIENT_TYPE_COUNT; j++) + server.stat_clients_type_memory[j] = 0; server.cron_malloc_stats.zmalloc_used = 0; server.cron_malloc_stats.process_rss = 0; server.cron_malloc_stats.allocator_allocated = 0; diff --git a/src/server.h b/src/server.h index 9b77f55ac..cf4c285f8 100644 --- a/src/server.h +++ b/src/server.h @@ -274,6 +274,7 @@ typedef long long ustime_t; /* microsecond time type. */ #define CLIENT_TYPE_SLAVE 1 /* Slaves. */ #define CLIENT_TYPE_PUBSUB 2 /* Clients subscribed to PubSub channels. */ #define CLIENT_TYPE_MASTER 3 /* Master. */ +#define CLIENT_TYPE_COUNT 4 /* Total number of client types. */ #define CLIENT_TYPE_OBUF_COUNT 3 /* Number of clients to expose to output buffer configuration. Just the first three: normal, slave, pubsub. */ @@ -820,10 +821,10 @@ typedef struct client { * when the authenticated user * changes. */ void *auth_callback_privdata; /* Private data that is passed when the auth - * changed callback is executed. Opaque for + * changed callback is executed. Opaque for * Redis Core. */ void *auth_module; /* The module that owns the callback, which is used - * to disconnect the client if the module is + * to disconnect the client if the module is * unloaded for cleanup. Opaque for Redis Core.*/ /* If this client is in tracking mode and this field is non zero, @@ -833,6 +834,13 @@ typedef struct client { rax *client_tracking_prefixes; /* A dictionary of prefixes we are already subscribed to in BCAST mode, in the context of client side caching. */ + /* In clientsCronTrackClientsMemUsage() we track the memory usage of + * each client and add it to the sum of all the clients of a given type, + * however we need to remember what was the old contribution of each + * client, and in which categoty the client was, in order to remove it + * before adding it the new value. */ + uint64_t client_cron_last_memory_usage; + int client_cron_last_memory_type; /* Response buffer */ int bufpos; char buf[PROTO_REPLY_CHUNK_BYTES]; @@ -1129,6 +1137,7 @@ struct redisServer { size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */ size_t stat_aof_cow_bytes; /* Copy on write bytes during AOF rewrite. */ size_t stat_module_cow_bytes; /* Copy on write bytes during module fork. */ + uint64_t stat_clients_type_memory[CLIENT_TYPE_COUNT];/* Mem usage by type */ long long stat_unexpected_error_replies; /* Number of unexpected (aof-loading, replica to master, etc.) error replies */ /* The following two are used to track instantaneous metrics, like * number of operations per second, network traffic. */