diff --git a/src/StackExchange.Redis/Enums/RedisCommand.cs b/src/StackExchange.Redis/Enums/RedisCommand.cs
index 41656cbb4..7d199e564 100644
--- a/src/StackExchange.Redis/Enums/RedisCommand.cs
+++ b/src/StackExchange.Redis/Enums/RedisCommand.cs
@@ -76,6 +76,7 @@ internal enum RedisCommand
KEYS,
LASTSAVE,
+ LATENCY,
LINDEX,
LINSERT,
LLEN,
@@ -87,6 +88,7 @@ internal enum RedisCommand
LSET,
LTRIM,
+ MEMORY,
MGET,
MIGRATE,
MONITOR,
diff --git a/src/StackExchange.Redis/Interfaces/IServer.cs b/src/StackExchange.Redis/Interfaces/IServer.cs
index 48e61d4f9..1eec8bdb5 100644
--- a/src/StackExchange.Redis/Interfaces/IServer.cs
+++ b/src/StackExchange.Redis/Interfaces/IServer.cs
@@ -622,6 +622,98 @@ public partial interface IServer : IRedis
/// https://redis.io/commands/time
Task TimeAsync(CommandFlags flags = CommandFlags.None);
+ ///
+ /// Gets a text-based latency diagnostic
+ ///
+ /// https://redis.io/topics/latency-monitor
+ Task LatencyDoctorAsync(CommandFlags flags = CommandFlags.None);
+ ///
+ /// Gets a text-based latency diagnostic
+ ///
+ /// https://redis.io/topics/latency-monitor
+ string LatencyDoctor(CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Resets the given events (or all if none are specified), discarding the currently logged latency spike events, and resetting the maximum event time register.
+ ///
+ /// https://redis.io/topics/latency-monitor
+ Task LatencyResetAsync(string[] eventNames = null, CommandFlags flags = CommandFlags.None);
+ ///
+ /// Resets the given events (or all if none are specified), discarding the currently logged latency spike events, and resetting the maximum event time register.
+ ///
+ /// https://redis.io/topics/latency-monitor
+ long LatencyReset(string[] eventNames = null, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Fetch raw latency data from the event time series, as timestamp-latency pairs
+ ///
+ /// https://redis.io/topics/latency-monitor
+ Task LatencyHistoryAsync(string eventName, CommandFlags flags = CommandFlags.None);
+ ///
+ /// Fetch raw latency data from the event time series, as timestamp-latency pairs
+ ///
+ /// https://redis.io/topics/latency-monitor
+ LatencyHistoryEntry[] LatencyHistory(string eventName, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Fetch raw latency data from the event time series, as timestamp-latency pairs
+ ///
+ /// https://redis.io/topics/latency-monitor
+ Task LatencyLatestAsync(CommandFlags flags = CommandFlags.None);
+ ///
+ /// Fetch raw latency data from the event time series, as timestamp-latency pairs
+ ///
+ /// https://redis.io/topics/latency-monitor
+ LatencyLatestEntry[] LatencyLatest(CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Reports about different memory-related issues that the Redis server experiences, and advises about possible remedies.
+ ///
+ /// https://redis.io/commands/memory-doctor
+ Task MemoryDoctorAsync(CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Reports about different memory-related issues that the Redis server experiences, and advises about possible remedies.
+ ///
+ /// https://redis.io/commands/memory-doctor
+ string MemoryDoctor(CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Attempts to purge dirty pages so these can be reclaimed by the allocator.
+ ///
+ /// https://redis.io/commands/memory-purge
+ Task MemoryPurgeAsync(CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Attempts to purge dirty pages so these can be reclaimed by the allocator.
+ ///
+ /// https://redis.io/commands/memory-purge
+ void MemoryPurge(CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Returns an array reply about the memory usage of the server.
+ ///
+ /// https://redis.io/commands/memory-stats
+ Task MemoryStatsAsync(CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Returns an array reply about the memory usage of the server.
+ ///
+ /// https://redis.io/commands/memory-stats
+ RedisResult MemoryStats(CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Provides an internal statistics report from the memory allocator.
+ ///
+ /// https://redis.io/commands/memory-malloc-stats
+ Task MemoryAllocatorStatsAsync(CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Provides an internal statistics report from the memory allocator.
+ ///
+ /// https://redis.io/commands/memory-malloc-stats
+ string MemoryAllocatorStats(CommandFlags flags = CommandFlags.None);
+
#region Sentinel
///
@@ -734,6 +826,107 @@ public partial interface IServer : IRedis
#endregion
}
+ ///
+ /// A latency entry as reported by the built-in LATENCY HISTORY command
+ ///
+ public readonly struct LatencyHistoryEntry
+ {
+ internal static readonly ResultProcessor ToArray = new Processor();
+
+ private sealed class Processor : ArrayResultProcessor
+ {
+ protected override bool TryParse(in RawResult raw, out LatencyHistoryEntry parsed)
+ {
+ if (raw.Type == ResultType.MultiBulk)
+ {
+ var items = raw.GetItems();
+ if (items.Length >= 2
+ && items[0].TryGetInt64(out var timestamp)
+ && items[1].TryGetInt64(out var duration))
+ {
+ parsed = new LatencyHistoryEntry(timestamp, duration);
+ return true;
+ }
+ }
+ parsed = default;
+ return false;
+ }
+ }
+
+ ///
+ /// The time at which this entry was recorded
+ ///
+ public DateTime Timestamp { get; }
+
+ ///
+ /// The latency recorded for this event
+ ///
+ public int DurationMilliseconds { get; }
+
+ internal LatencyHistoryEntry(long timestamp, long duration)
+ {
+ Timestamp = RedisBase.UnixEpoch.AddSeconds(timestamp);
+ DurationMilliseconds = checked((int)duration);
+ }
+ }
+
+ ///
+ /// A latency entry as reported by the built-in LATENCY LATEST command
+ ///
+ public readonly struct LatencyLatestEntry
+ {
+ internal static readonly ResultProcessor ToArray = new Processor();
+
+ private sealed class Processor : ArrayResultProcessor
+ {
+ protected override bool TryParse(in RawResult raw, out LatencyLatestEntry parsed)
+ {
+ if (raw.Type == ResultType.MultiBulk)
+ {
+ var items = raw.GetItems();
+ if (items.Length >= 4
+ && items[1].TryGetInt64(out var timestamp)
+ && items[2].TryGetInt64(out var duration)
+ && items[3].TryGetInt64(out var maxDuration))
+ {
+ parsed = new LatencyLatestEntry(items[0].GetString(), timestamp, duration, maxDuration);
+ return true;
+ }
+ }
+ parsed = default;
+ return false;
+ }
+ }
+
+ ///
+ /// The name of this event
+ ///
+ public string EventName { get; }
+
+ ///
+ /// The time at which this entry was recorded
+ ///
+ public DateTime Timestamp { get; }
+
+ ///
+ /// The latency recorded for this event
+ ///
+ public int DurationMilliseconds { get; }
+
+ ///
+ /// The max latency recorded for all events
+ ///
+ public int MaxDurationMilliseconds { get; }
+
+ internal LatencyLatestEntry(string eventName, long timestamp, long duration, long maxDuration)
+ {
+ EventName = eventName;
+ Timestamp = RedisBase.UnixEpoch.AddSeconds(timestamp);
+ DurationMilliseconds = checked((int)duration);
+ MaxDurationMilliseconds = checked((int)maxDuration);
+ }
+ }
+
internal static class IServerExtensions
{
///
diff --git a/src/StackExchange.Redis/Message.cs b/src/StackExchange.Redis/Message.cs
index b5f36dd45..2113ee5c6 100644
--- a/src/StackExchange.Redis/Message.cs
+++ b/src/StackExchange.Redis/Message.cs
@@ -549,6 +549,8 @@ internal static bool RequiresDatabase(RedisCommand command)
case RedisCommand.FLUSHALL:
case RedisCommand.INFO:
case RedisCommand.LASTSAVE:
+ case RedisCommand.LATENCY:
+ case RedisCommand.MEMORY:
case RedisCommand.MONITOR:
case RedisCommand.MULTI:
case RedisCommand.PING:
diff --git a/src/StackExchange.Redis/RedisFeatures.cs b/src/StackExchange.Redis/RedisFeatures.cs
index 8d03b03f9..9d5223a5e 100644
--- a/src/StackExchange.Redis/RedisFeatures.cs
+++ b/src/StackExchange.Redis/RedisFeatures.cs
@@ -90,6 +90,11 @@ public RedisFeatures(Version version)
///
public bool ListInsert => Version >= v2_1_1;
+ ///
+ /// Is MEMORY available?
+ ///
+ public bool Memory => Version >= v4_0_0;
+
///
/// Indicates whether PEXPIRE and PTTL are supported
///
diff --git a/src/StackExchange.Redis/RedisLiterals.cs b/src/StackExchange.Redis/RedisLiterals.cs
index 609800668..1dda150cc 100644
--- a/src/StackExchange.Redis/RedisLiterals.cs
+++ b/src/StackExchange.Redis/RedisLiterals.cs
@@ -51,18 +51,22 @@ public static readonly RedisValue
COPY = "COPY",
COUNT = "COUNT",
DESC = "DESC",
+ DOCTOR = "DOCTOR",
EX = "EX",
EXISTS = "EXISTS",
FLUSH = "FLUSH",
GET = "GET",
GETNAME = "GETNAME",
+ HISTORY = "HISTORY",
ID = "ID",
IDLETIME = "IDLETIME",
KILL = "KILL",
+ LATEST = "LATEST",
LIMIT = "LIMIT",
LIST = "LIST",
LOAD = "LOAD",
MATCH = "MATCH",
+ MALLOC_STATS = "MALLOC-STATS",
MAX = "MAX",
MIN = "MIN",
NODES = "NODES",
@@ -75,6 +79,7 @@ public static readonly RedisValue
OR = "OR",
PAUSE = "PAUSE",
PING = "PING",
+ PURGE = "PURGE",
PX = "PX",
REPLACE = "REPLACE",
RESET = "RESET",
@@ -85,6 +90,7 @@ public static readonly RedisValue
SET = "SET",
SETNAME = "SETNAME",
SKIPME = "SKIPME",
+ STATS = "STATS",
STORE = "STORE",
TYPE = "TYPE",
WEIGHTS = "WEIGHTS",
diff --git a/src/StackExchange.Redis/RedisResult.cs b/src/StackExchange.Redis/RedisResult.cs
index d88868448..b1c3d51b1 100644
--- a/src/StackExchange.Redis/RedisResult.cs
+++ b/src/StackExchange.Redis/RedisResult.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
namespace StackExchange.Redis
{
@@ -61,7 +62,7 @@ internal static RedisResult TryCreate(PhysicalConnection connection, in RawResul
if (items.Length == 0) return EmptyArray;
var arr = new RedisResult[items.Length];
int i = 0;
- foreach(ref RawResult item in items)
+ foreach (ref RawResult item in items)
{
var next = TryCreate(connection, in item);
if (next == null) return null; // means we didn't understand
@@ -100,7 +101,7 @@ internal static RedisResult TryCreate(PhysicalConnection connection, in RawResul
/// Interprets the result as a .
///
/// The result to convert to a .
- public static explicit operator byte[] (RedisResult result) => result.AsByteArray();
+ public static explicit operator byte[](RedisResult result) => result.AsByteArray();
///
/// Interprets the result as a .
///
@@ -141,79 +142,94 @@ internal static RedisResult TryCreate(PhysicalConnection connection, in RawResul
/// Interprets the result as a .
///
/// The result to convert to a .
- public static explicit operator double? (RedisResult result) => result.AsNullableDouble();
+ public static explicit operator double?(RedisResult result) => result.AsNullableDouble();
///
/// Interprets the result as a .
///
/// The result to convert to a .
- public static explicit operator long? (RedisResult result) => result.AsNullableInt64();
+ public static explicit operator long?(RedisResult result) => result.AsNullableInt64();
///
/// Interprets the result as a .
///
/// The result to convert to a .
[CLSCompliant(false)]
- public static explicit operator ulong? (RedisResult result) => result.AsNullableUInt64();
+ public static explicit operator ulong?(RedisResult result) => result.AsNullableUInt64();
///
/// Interprets the result as a .
///
/// The result to convert to a .
- public static explicit operator int? (RedisResult result) => result.AsNullableInt32();
+ public static explicit operator int?(RedisResult result) => result.AsNullableInt32();
///
/// Interprets the result as a .
///
/// The result to convert to a .
- public static explicit operator bool? (RedisResult result) => result.AsNullableBoolean();
+ public static explicit operator bool?(RedisResult result) => result.AsNullableBoolean();
///
/// Interprets the result as a .
///
/// The result to convert to a .
- public static explicit operator string[] (RedisResult result) => result.AsStringArray();
+ public static explicit operator string[](RedisResult result) => result.AsStringArray();
///
/// Interprets the result as a .
///
/// The result to convert to a .
- public static explicit operator byte[][] (RedisResult result) => result.AsByteArrayArray();
+ public static explicit operator byte[][](RedisResult result) => result.AsByteArrayArray();
///
/// Interprets the result as a .
///
/// The result to convert to a .
- public static explicit operator double[] (RedisResult result) => result.AsDoubleArray();
+ public static explicit operator double[](RedisResult result) => result.AsDoubleArray();
///
/// Interprets the result as a .
///
/// The result to convert to a .
- public static explicit operator long[] (RedisResult result) => result.AsInt64Array();
+ public static explicit operator long[](RedisResult result) => result.AsInt64Array();
///
/// Interprets the result as a .
///
/// The result to convert to a .
[CLSCompliant(false)]
- public static explicit operator ulong[] (RedisResult result) => result.AsUInt64Array();
+ public static explicit operator ulong[](RedisResult result) => result.AsUInt64Array();
///
/// Interprets the result as a .
///
/// The result to convert to a .
- public static explicit operator int[] (RedisResult result) => result.AsInt32Array();
+ public static explicit operator int[](RedisResult result) => result.AsInt32Array();
///
/// Interprets the result as a .
///
/// The result to convert to a .
- public static explicit operator bool[] (RedisResult result) => result.AsBooleanArray();
+ public static explicit operator bool[](RedisResult result) => result.AsBooleanArray();
///
/// Interprets the result as a .
///
/// The result to convert to a .
- public static explicit operator RedisValue[] (RedisResult result) => result.AsRedisValueArray();
+ public static explicit operator RedisValue[](RedisResult result) => result.AsRedisValueArray();
///
/// Interprets the result as a .
///
/// The result to convert to a .
- public static explicit operator RedisKey[] (RedisResult result) => result.AsRedisKeyArray();
+ public static explicit operator RedisKey[](RedisResult result) => result.AsRedisKeyArray();
///
/// Interprets the result as a .
///
/// The result to convert to a .
- public static explicit operator RedisResult[] (RedisResult result) => result.AsRedisResultArray();
+ public static explicit operator RedisResult[](RedisResult result) => result.AsRedisResultArray();
+
+ ///
+ /// Interprets a multi-bulk result with successive key/name values as a dictionary keyed by name
+ ///
+ public Dictionary ToDictionary(IEqualityComparer comparer = null)
+ {
+ var arr = AsRedisResultArray();
+ int len = arr.Length / 2;
+ var result = new Dictionary(len, comparer ?? StringComparer.InvariantCultureIgnoreCase);
+ for (int i = 0; i < len;)
+ {
+ result.Add(arr[i++].AsString(), arr[i++]);
+ }
+ return result;
+ }
internal abstract bool AsBoolean();
internal abstract bool[] AsBooleanArray();
diff --git a/src/StackExchange.Redis/RedisServer.cs b/src/StackExchange.Redis/RedisServer.cs
index e30870dae..c2d69e945 100644
--- a/src/StackExchange.Redis/RedisServer.cs
+++ b/src/StackExchange.Redis/RedisServer.cs
@@ -869,5 +869,119 @@ public Task ExecuteAsync(string command, ICollection