feat(batch27): implement jetstream usage foundations and limit helpers

This commit is contained in:
Joseph Doherty
2026-02-28 21:07:24 -05:00
parent 12b8c9b4c5
commit 6726c07749
4 changed files with 452 additions and 0 deletions

View File

@@ -250,6 +250,162 @@ public sealed partial class Account
return null;
}
public JetStreamAccountStats JetStreamUsage()
{
_mu.EnterReadLock();
var jsa = JetStream;
var accountName = Name;
var configuredLimits = JetStreamLimits;
_mu.ExitReadLock();
var stats = new JetStreamAccountStats();
if (jsa == null)
return stats;
var (js, _) = jsa.JetStreamAndClustered();
if (js == null)
return stats;
jsa.UsageLock.EnterReadLock();
try
{
long mem = 0;
long store = 0;
foreach (var usage in jsa.Usage.Values)
{
mem += usage.Total.Mem;
store += usage.Total.Store;
}
stats.Memory = (ulong)Math.Max(0, mem);
stats.Store = (ulong)Math.Max(0, store);
stats.Domain = js.Config.Domain;
stats.Api = new JetStreamApiStats
{
Level = JetStreamVersioning.JsApiLevel,
Total = jsa.ApiTotal,
Errors = jsa.ApiErrors,
};
if (jsa.Limits.TryGetValue(string.Empty, out var defaultTier))
{
stats.Limits = defaultTier;
}
else
{
stats.Tiers = new Dictionary<string, JetStreamTier>(StringComparer.Ordinal);
foreach (var (tier, usage) in jsa.Usage)
{
jsa.Limits.TryGetValue(tier, out var tierLimits);
stats.Tiers[tier] = new JetStreamTier
{
Memory = (ulong)Math.Max(0, usage.Total.Mem),
Store = (ulong)Math.Max(0, usage.Total.Store),
Limits = tierLimits ?? new JetStreamAccountLimits(),
};
}
if (configuredLimits != null)
{
foreach (var (tier, value) in configuredLimits)
{
if (stats.Tiers.ContainsKey(tier))
continue;
if (value is not JetStreamAccountLimits lim)
continue;
stats.Tiers[tier] = new JetStreamTier { Limits = lim };
}
}
}
}
finally
{
jsa.UsageLock.ExitReadLock();
}
var allStreams = Streams();
stats.Streams = allStreams.Count;
foreach (var stream in allStreams)
stats.Consumers += stream.State().Consumers;
if (stats.Tiers != null)
{
foreach (var stream in allStreams)
{
var tier = JetStreamEngine.TierName(stream.Config.Replicas);
if (!stats.Tiers.TryGetValue(tier, out var u))
u = new JetStreamTier();
u.Streams++;
u.Consumers += stream.State().Consumers;
stats.Tiers[tier] = u;
}
}
if (stats.Tiers == null || stats.Tiers.Count == 0)
{
var (rmem, rstore) = jsa.ReservedStorage(string.Empty);
stats.ReservedMemory = rmem;
stats.ReservedStore = rstore;
}
else
{
foreach (var tier in stats.Tiers.Keys.ToArray())
{
var tierStats = stats.Tiers[tier];
(tierStats.ReservedMemory, tierStats.ReservedStore) = jsa.ReservedStorage(tier);
stats.Tiers[tier] = tierStats;
}
}
_ = accountName;
return stats;
}
internal Exception? DisableJetStream() => RemoveJetStream();
internal Exception? RemoveJetStream()
{
_mu.EnterWriteLock();
var server = Server as NatsServer;
var jsa = JetStream;
JetStream = null;
_mu.ExitWriteLock();
if (server == null)
return new InvalidOperationException("jetstream account not registered");
var js = server.GetJetStream();
if (js == null)
return new InvalidOperationException("jetstream not enabled for account");
return js.DisableJetStream(jsa);
}
internal bool JetStreamConfigured()
{
_mu.EnterReadLock();
try
{
return JetStreamLimits != null && JetStreamLimits.Count > 0;
}
finally
{
_mu.ExitReadLock();
}
}
internal bool JetStreamEnabled()
{
_mu.EnterReadLock();
try
{
return JetStream != null;
}
finally
{
_mu.ExitReadLock();
}
}
internal Exception? EnableAllJetStreamServiceImportsAndMappings()
{
_mu.EnterReadLock();