Skip to content

Commit

Permalink
Add support for the LATENCY and MEMORY commands (#1204)
Browse files Browse the repository at this point in the history
Adds LATENCY and MEMORY command support and testing.
  • Loading branch information
mgravell authored and NickCraver committed Oct 19, 2019
1 parent b0a07a7 commit 8672ca2
Show file tree
Hide file tree
Showing 11 changed files with 563 additions and 20 deletions.
2 changes: 2 additions & 0 deletions src/StackExchange.Redis/Enums/RedisCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ internal enum RedisCommand
KEYS,

LASTSAVE,
LATENCY,
LINDEX,
LINSERT,
LLEN,
Expand All @@ -87,6 +88,7 @@ internal enum RedisCommand
LSET,
LTRIM,

MEMORY,
MGET,
MIGRATE,
MONITOR,
Expand Down
193 changes: 193 additions & 0 deletions src/StackExchange.Redis/Interfaces/IServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,98 @@ public partial interface IServer : IRedis
/// <remarks>https://redis.io/commands/time</remarks>
Task<DateTime> TimeAsync(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Gets a text-based latency diagnostic
/// </summary>
/// <remarks>https://redis.io/topics/latency-monitor</remarks>
Task<string> LatencyDoctorAsync(CommandFlags flags = CommandFlags.None);
/// <summary>
/// Gets a text-based latency diagnostic
/// </summary>
/// <remarks>https://redis.io/topics/latency-monitor</remarks>
string LatencyDoctor(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Resets the given events (or all if none are specified), discarding the currently logged latency spike events, and resetting the maximum event time register.
/// </summary>
/// <remarks>https://redis.io/topics/latency-monitor</remarks>
Task<long> LatencyResetAsync(string[] eventNames = null, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Resets the given events (or all if none are specified), discarding the currently logged latency spike events, and resetting the maximum event time register.
/// </summary>
/// <remarks>https://redis.io/topics/latency-monitor</remarks>
long LatencyReset(string[] eventNames = null, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Fetch raw latency data from the event time series, as timestamp-latency pairs
/// </summary>
/// <remarks>https://redis.io/topics/latency-monitor</remarks>
Task<LatencyHistoryEntry[]> LatencyHistoryAsync(string eventName, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Fetch raw latency data from the event time series, as timestamp-latency pairs
/// </summary>
/// <remarks>https://redis.io/topics/latency-monitor</remarks>
LatencyHistoryEntry[] LatencyHistory(string eventName, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Fetch raw latency data from the event time series, as timestamp-latency pairs
/// </summary>
/// <remarks>https://redis.io/topics/latency-monitor</remarks>
Task<LatencyLatestEntry[]> LatencyLatestAsync(CommandFlags flags = CommandFlags.None);
/// <summary>
/// Fetch raw latency data from the event time series, as timestamp-latency pairs
/// </summary>
/// <remarks>https://redis.io/topics/latency-monitor</remarks>
LatencyLatestEntry[] LatencyLatest(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Reports about different memory-related issues that the Redis server experiences, and advises about possible remedies.
/// </summary>
/// <remarks>https://redis.io/commands/memory-doctor</remarks>
Task<string> MemoryDoctorAsync(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Reports about different memory-related issues that the Redis server experiences, and advises about possible remedies.
/// </summary>
/// <remarks>https://redis.io/commands/memory-doctor</remarks>
string MemoryDoctor(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Attempts to purge dirty pages so these can be reclaimed by the allocator.
/// </summary>
/// <remarks>https://redis.io/commands/memory-purge</remarks>
Task MemoryPurgeAsync(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Attempts to purge dirty pages so these can be reclaimed by the allocator.
/// </summary>
/// <remarks>https://redis.io/commands/memory-purge</remarks>
void MemoryPurge(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Returns an array reply about the memory usage of the server.
/// </summary>
/// <remarks>https://redis.io/commands/memory-stats</remarks>
Task<RedisResult> MemoryStatsAsync(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Returns an array reply about the memory usage of the server.
/// </summary>
/// <remarks>https://redis.io/commands/memory-stats</remarks>
RedisResult MemoryStats(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Provides an internal statistics report from the memory allocator.
/// </summary>
/// <remarks>https://redis.io/commands/memory-malloc-stats</remarks>
Task<string> MemoryAllocatorStatsAsync(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Provides an internal statistics report from the memory allocator.
/// </summary>
/// <remarks>https://redis.io/commands/memory-malloc-stats</remarks>
string MemoryAllocatorStats(CommandFlags flags = CommandFlags.None);

#region Sentinel

/// <summary>
Expand Down Expand Up @@ -734,6 +826,107 @@ public partial interface IServer : IRedis
#endregion
}

/// <summary>
/// A latency entry as reported by the built-in LATENCY HISTORY command
/// </summary>
public readonly struct LatencyHistoryEntry
{
internal static readonly ResultProcessor<LatencyHistoryEntry[]> ToArray = new Processor();

private sealed class Processor : ArrayResultProcessor<LatencyHistoryEntry>
{
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;
}
}

/// <summary>
/// The time at which this entry was recorded
/// </summary>
public DateTime Timestamp { get; }

/// <summary>
/// The latency recorded for this event
/// </summary>
public int DurationMilliseconds { get; }

internal LatencyHistoryEntry(long timestamp, long duration)
{
Timestamp = RedisBase.UnixEpoch.AddSeconds(timestamp);
DurationMilliseconds = checked((int)duration);
}
}

/// <summary>
/// A latency entry as reported by the built-in LATENCY LATEST command
/// </summary>
public readonly struct LatencyLatestEntry
{
internal static readonly ResultProcessor<LatencyLatestEntry[]> ToArray = new Processor();

private sealed class Processor : ArrayResultProcessor<LatencyLatestEntry>
{
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;
}
}

/// <summary>
/// The name of this event
/// </summary>
public string EventName { get; }

/// <summary>
/// The time at which this entry was recorded
/// </summary>
public DateTime Timestamp { get; }

/// <summary>
/// The latency recorded for this event
/// </summary>
public int DurationMilliseconds { get; }

/// <summary>
/// The max latency recorded for all events
/// </summary>
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
{
/// <summary>
Expand Down
2 changes: 2 additions & 0 deletions src/StackExchange.Redis/Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
5 changes: 5 additions & 0 deletions src/StackExchange.Redis/RedisFeatures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ public RedisFeatures(Version version)
/// </summary>
public bool ListInsert => Version >= v2_1_1;

/// <summary>
/// Is MEMORY available?
/// </summary>
public bool Memory => Version >= v4_0_0;

/// <summary>
/// Indicates whether PEXPIRE and PTTL are supported
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions src/StackExchange.Redis/RedisLiterals.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -75,6 +79,7 @@ public static readonly RedisValue
OR = "OR",
PAUSE = "PAUSE",
PING = "PING",
PURGE = "PURGE",
PX = "PX",
REPLACE = "REPLACE",
RESET = "RESET",
Expand All @@ -85,6 +90,7 @@ public static readonly RedisValue
SET = "SET",
SETNAME = "SETNAME",
SKIPME = "SKIPME",
STATS = "STATS",
STORE = "STORE",
TYPE = "TYPE",
WEIGHTS = "WEIGHTS",
Expand Down
Loading

0 comments on commit 8672ca2

Please sign in to comment.