forked from valkey-io/valkey
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce CLUSTER SLOT-STATS command (valkey-io#20). (valkey-io#351)
The command provides detailed slot usage statistics upon invocation, with initial support for key-count metric. cpu-usec (approved) and memory-bytes (pending-approval) metrics will soon follow after the merger of this PR. --------- Signed-off-by: Kyle Kim <[email protected]> Signed-off-by: Madelyn Olson <[email protected]> Co-authored-by: Madelyn Olson <[email protected]>
- Loading branch information
1 parent
7719dbb
commit 1269532
Showing
7 changed files
with
662 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
/* | ||
* Copyright Valkey Contributors. | ||
* All rights reserved. | ||
* SPDX-License-Identifier: BSD 3-Clause | ||
*/ | ||
|
||
#include "server.h" | ||
#include "cluster.h" | ||
|
||
#define UNASSIGNED_SLOT 0 | ||
|
||
typedef enum { | ||
KEY_COUNT, | ||
INVALID, | ||
} slotStatTypes; | ||
|
||
/* ----------------------------------------------------------------------------- | ||
* CLUSTER SLOT-STATS command | ||
* -------------------------------------------------------------------------- */ | ||
|
||
/* Struct used to temporarily hold slot statistics for sorting. */ | ||
typedef struct { | ||
int slot; | ||
uint64_t stat; | ||
} slotStatForSort; | ||
|
||
static int doesSlotBelongToMyShard(int slot) { | ||
clusterNode *myself = getMyClusterNode(); | ||
clusterNode *primary = clusterNodeGetPrimary(myself); | ||
|
||
return clusterNodeCoversSlot(primary, slot); | ||
} | ||
|
||
static int markSlotsAssignedToMyShard(unsigned char *assigned_slots, int start_slot, int end_slot) { | ||
int assigned_slots_count = 0; | ||
for (int slot = start_slot; slot <= end_slot; slot++) { | ||
if (doesSlotBelongToMyShard(slot)) { | ||
assigned_slots[slot]++; | ||
assigned_slots_count++; | ||
} | ||
} | ||
return assigned_slots_count; | ||
} | ||
|
||
static uint64_t getSlotStat(int slot, int stat_type) { | ||
serverAssert(stat_type != INVALID); | ||
uint64_t slot_stat = 0; | ||
if (stat_type == KEY_COUNT) { | ||
slot_stat = countKeysInSlot(slot); | ||
} | ||
return slot_stat; | ||
} | ||
|
||
static int slotStatForSortAscCmp(const void *a, const void *b) { | ||
slotStatForSort entry_a = *((slotStatForSort *)a); | ||
slotStatForSort entry_b = *((slotStatForSort *)b); | ||
return entry_a.stat - entry_b.stat; | ||
} | ||
|
||
static int slotStatForSortDescCmp(const void *a, const void *b) { | ||
slotStatForSort entry_a = *((slotStatForSort *)a); | ||
slotStatForSort entry_b = *((slotStatForSort *)b); | ||
return entry_b.stat - entry_a.stat; | ||
} | ||
|
||
static void collectAndSortSlotStats(slotStatForSort slot_stats[], int order_by, int desc) { | ||
int i = 0; | ||
|
||
for (int slot = 0; slot < CLUSTER_SLOTS; slot++) { | ||
if (doesSlotBelongToMyShard(slot)) { | ||
slot_stats[i].slot = slot; | ||
slot_stats[i].stat = getSlotStat(slot, order_by); | ||
i++; | ||
} | ||
} | ||
qsort(slot_stats, i, sizeof(slotStatForSort), (desc) ? slotStatForSortDescCmp : slotStatForSortAscCmp); | ||
} | ||
|
||
static void addReplySlotStat(client *c, int slot) { | ||
addReplyArrayLen(c, 2); /* Array of size 2, where 0th index represents (int) slot, | ||
* and 1st index represents (map) usage statistics. */ | ||
addReplyLongLong(c, slot); | ||
addReplyMapLen(c, 1); /* Nested map representing slot usage statistics. */ | ||
addReplyBulkCString(c, "key-count"); | ||
addReplyLongLong(c, countKeysInSlot(slot)); | ||
} | ||
|
||
/* Adds reply for the SLOTSRANGE variant. | ||
* Response is ordered in ascending slot number. */ | ||
static void addReplySlotsRange(client *c, unsigned char *assigned_slots, int startslot, int endslot, int len) { | ||
addReplyArrayLen(c, len); /* Top level RESP reply format is defined as an array, due to ordering invariance. */ | ||
|
||
for (int slot = startslot; slot <= endslot; slot++) { | ||
if (assigned_slots[slot]) addReplySlotStat(c, slot); | ||
} | ||
} | ||
|
||
static void addReplySortedSlotStats(client *c, slotStatForSort slot_stats[], long limit) { | ||
int num_slots_assigned = getMyShardSlotCount(); | ||
int len = min(limit, num_slots_assigned); | ||
addReplyArrayLen(c, len); /* Top level RESP reply format is defined as an array, due to ordering invariance. */ | ||
|
||
for (int i = 0; i < len; i++) { | ||
addReplySlotStat(c, slot_stats[i].slot); | ||
} | ||
} | ||
|
||
/* Adds reply for the ORDERBY variant. | ||
* Response is ordered based on the sort result. */ | ||
static void addReplyOrderBy(client *c, int order_by, long limit, int desc) { | ||
slotStatForSort slot_stats[CLUSTER_SLOTS]; | ||
collectAndSortSlotStats(slot_stats, order_by, desc); | ||
addReplySortedSlotStats(c, slot_stats, limit); | ||
} | ||
|
||
void clusterSlotStatsCommand(client *c) { | ||
if (server.cluster_enabled == 0) { | ||
addReplyError(c, "This instance has cluster support disabled"); | ||
return; | ||
} | ||
|
||
/* Parse additional arguments. */ | ||
if (c->argc == 5 && !strcasecmp(c->argv[2]->ptr, "slotsrange")) { | ||
/* CLUSTER SLOT-STATS SLOTSRANGE start-slot end-slot */ | ||
int startslot, endslot; | ||
if ((startslot = getSlotOrReply(c, c->argv[3])) == C_ERR || | ||
(endslot = getSlotOrReply(c, c->argv[4])) == C_ERR) { | ||
return; | ||
} | ||
if (startslot > endslot) { | ||
addReplyErrorFormat(c, "Start slot number %d is greater than end slot number %d", startslot, endslot); | ||
return; | ||
} | ||
/* Initialize slot assignment array. */ | ||
unsigned char assigned_slots[CLUSTER_SLOTS] = {UNASSIGNED_SLOT}; | ||
int assigned_slots_count = markSlotsAssignedToMyShard(assigned_slots, startslot, endslot); | ||
addReplySlotsRange(c, assigned_slots, startslot, endslot, assigned_slots_count); | ||
|
||
} else if (c->argc >= 4 && !strcasecmp(c->argv[2]->ptr, "orderby")) { | ||
/* CLUSTER SLOT-STATS ORDERBY metric [LIMIT limit] [ASC | DESC] */ | ||
int desc = 1, order_by = INVALID; | ||
if (!strcasecmp(c->argv[3]->ptr, "key-count")) { | ||
order_by = KEY_COUNT; | ||
} else { | ||
addReplyError(c, "Unrecognized sort metric for ORDER BY. The supported metrics are: key-count."); | ||
return; | ||
} | ||
int i = 4; /* Next argument index, following ORDERBY */ | ||
int limit_counter = 0, asc_desc_counter = 0; | ||
long limit; | ||
while (i < c->argc) { | ||
int moreargs = c->argc > i + 1; | ||
if (!strcasecmp(c->argv[i]->ptr, "limit") && moreargs) { | ||
if (getRangeLongFromObjectOrReply( | ||
c, c->argv[i + 1], 1, CLUSTER_SLOTS, &limit, | ||
"Limit has to lie in between 1 and 16384 (maximum number of slots).") != C_OK) { | ||
return; | ||
} | ||
i++; | ||
limit_counter++; | ||
} else if (!strcasecmp(c->argv[i]->ptr, "asc")) { | ||
desc = 0; | ||
asc_desc_counter++; | ||
} else if (!strcasecmp(c->argv[i]->ptr, "desc")) { | ||
desc = 1; | ||
asc_desc_counter++; | ||
} else { | ||
addReplyErrorObject(c, shared.syntaxerr); | ||
return; | ||
} | ||
if (limit_counter > 1 || asc_desc_counter > 1) { | ||
addReplyError(c, "Multiple filters of the same type are disallowed."); | ||
return; | ||
} | ||
i++; | ||
} | ||
addReplyOrderBy(c, order_by, limit, desc); | ||
|
||
} else { | ||
addReplySubcommandSyntaxError(c); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
{ | ||
"SLOT-STATS": { | ||
"summary": "Return an array of slot usage statistics for slots assigned to the current node.", | ||
"complexity": "O(N) where N is the total number of slots based on arguments. O(N*log(N)) with ORDERBY subcommand.", | ||
"group": "cluster", | ||
"since": "8.0.0", | ||
"arity": -4, | ||
"container": "CLUSTER", | ||
"function": "clusterSlotStatsCommand", | ||
"command_flags": [ | ||
"STALE", | ||
"LOADING" | ||
], | ||
"command_tips": [ | ||
"NONDETERMINISTIC_OUTPUT", | ||
"REQUEST_POLICY:ALL_SHARDS" | ||
], | ||
"reply_schema": { | ||
"type": "array", | ||
"description": "Array of nested arrays, where the inner array element represents a slot and its respective usage statistics.", | ||
"items": { | ||
"type": "array", | ||
"description": "Array of size 2, where 0th index represents (int) slot and 1st index represents (map) usage statistics.", | ||
"minItems": 2, | ||
"maxItems": 2, | ||
"items": [ | ||
{ | ||
"description": "Slot Number.", | ||
"type": "integer" | ||
}, | ||
{ | ||
"type": "object", | ||
"description": "Map of slot usage statistics.", | ||
"additionalProperties": false, | ||
"properties": { | ||
"key-count": { | ||
"type": "integer" | ||
} | ||
} | ||
} | ||
] | ||
} | ||
}, | ||
"arguments": [ | ||
{ | ||
"name": "filter", | ||
"type": "oneof", | ||
"arguments": [ | ||
{ | ||
"token": "SLOTSRANGE", | ||
"name": "slotsrange", | ||
"type": "block", | ||
"arguments": [ | ||
{ | ||
"name": "start-slot", | ||
"type": "integer" | ||
}, | ||
{ | ||
"name": "end-slot", | ||
"type": "integer" | ||
} | ||
] | ||
}, | ||
{ | ||
"token": "ORDERBY", | ||
"name": "orderby", | ||
"type": "block", | ||
"arguments": [ | ||
{ | ||
"name": "metric", | ||
"type": "string" | ||
}, | ||
{ | ||
"token": "LIMIT", | ||
"name": "limit", | ||
"type": "integer", | ||
"optional": true | ||
}, | ||
{ | ||
"name": "order", | ||
"type": "oneof", | ||
"optional": true, | ||
"arguments": [ | ||
{ | ||
"name": "asc", | ||
"type": "pure-token", | ||
"token": "ASC" | ||
}, | ||
{ | ||
"name": "desc", | ||
"type": "pure-token", | ||
"token": "DESC" | ||
} | ||
] | ||
} | ||
] | ||
} | ||
] | ||
} | ||
] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.