using System; using System.Threading; using Microsoft.Extensions.Logging; namespace FrostFS.SDK.ClientV2; // clientStatusMonitor count error rate and other statistics for connection. public class ClientStatusMonitor : IClientStatus { private static readonly MethodIndex[] MethodIndexes = [ MethodIndex.methodBalanceGet, MethodIndex.methodContainerPut, MethodIndex.methodContainerGet, MethodIndex.methodContainerList, MethodIndex.methodContainerDelete, MethodIndex.methodEndpointInfo, MethodIndex.methodNetworkInfo, MethodIndex.methodNetMapSnapshot, MethodIndex.methodObjectPut, MethodIndex.methodObjectDelete, MethodIndex.methodObjectGet, MethodIndex.methodObjectHead, MethodIndex.methodObjectRange, MethodIndex.methodObjectPatch, MethodIndex.methodSessionCreate, MethodIndex.methodAPEManagerAddChain, MethodIndex.methodAPEManagerRemoveChain, MethodIndex.methodAPEManagerListChains ]; public static string GetMethodName(MethodIndex index) { return index switch { MethodIndex.methodBalanceGet => "BalanceGet", MethodIndex.methodContainerPut => "ContainerPut", MethodIndex.methodContainerGet => "ContainerGet", MethodIndex.methodContainerList => "ContainerList", MethodIndex.methodContainerDelete => "ContainerDelete", MethodIndex.methodEndpointInfo => "EndpointInfo", MethodIndex.methodNetworkInfo => "NetworkInfo", MethodIndex.methodNetMapSnapshot => "NetMapSnapshot", MethodIndex.methodObjectPut => "ObjectPut", MethodIndex.methodObjectDelete => "ObjectDelete", MethodIndex.methodObjectGet => "ObjectGet", MethodIndex.methodObjectHead => "ObjectHead", MethodIndex.methodObjectRange => "ObjectRange", MethodIndex.methodObjectPatch => "ObjectPatch", MethodIndex.methodSessionCreate => "SessionCreate", MethodIndex.methodAPEManagerAddChain => "APEManagerAddChain", MethodIndex.methodAPEManagerRemoveChain => "APEManagerRemoveChain", MethodIndex.methodAPEManagerListChains => "APEManagerListChains", _ => throw new ArgumentException("Unknown method", nameof(index)), }; } private readonly object _lock = new(); private readonly ILogger? logger; private int healthy; public ClientStatusMonitor(ILogger? logger, string address) { this.logger = logger; healthy = (int)HealthyStatus.Healthy; Address = address; Methods = new MethodStatus[MethodIndexes.Length]; for (int i = 0; i < MethodIndexes.Length; i++) { Methods[i] = new MethodStatus(GetMethodName(MethodIndexes[i])); } } public string Address { get; } internal uint ErrorThreshold { get; set; } public uint CurrentErrorCount { get; set; } public ulong OverallErrorCount { get; set; } public MethodStatus[] Methods { get; private set; } public bool IsHealthy() { var res = Interlocked.CompareExchange(ref healthy, -1, -1) == (int)HealthyStatus.Healthy; return res; } public bool IsDialed() { return Interlocked.CompareExchange(ref healthy, -1, -1) != (int)HealthyStatus.UnhealthyOnDial; } public void SetHealthy() { Interlocked.Exchange(ref healthy, (int)HealthyStatus.Healthy); } public void SetUnhealthy() { Interlocked.Exchange(ref healthy, (int)HealthyStatus.UnhealthyOnRequest); } public void SetUnhealthyOnDial() { Interlocked.Exchange(ref healthy, (int)HealthyStatus.UnhealthyOnDial); } public void IncErrorRate() { bool thresholdReached; lock (_lock) { CurrentErrorCount++; OverallErrorCount++; thresholdReached = CurrentErrorCount >= ErrorThreshold; if (thresholdReached) { SetUnhealthy(); CurrentErrorCount = 0; } } if (thresholdReached && logger != null) { FrostFsMessages.ErrorЕhresholdReached(logger, Address, ErrorThreshold); } } public uint GetCurrentErrorRate() { lock (_lock) { return CurrentErrorCount; } } public ulong GetOverallErrorRate() { lock (_lock) { return OverallErrorCount; } } public StatusSnapshot[] MethodsStatus() { var result = new StatusSnapshot[Methods.Length]; for (int i = 0; i < result.Length; i++) { result[i] = Methods[i].Snapshot!; } return result; } }