Add new health
This commit is contained in:
114
src/Managing.Api/HealthChecks/CandleDataHealthCheck.cs
Normal file
114
src/Managing.Api/HealthChecks/CandleDataHealthCheck.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Api.HealthChecks
|
||||
{
|
||||
public class CandleDataHealthCheck : IHealthCheck
|
||||
{
|
||||
private readonly IExchangeService _exchangeService;
|
||||
private readonly Ticker _tickerToCheck = Ticker.ETH;
|
||||
private readonly TradingExchanges _exchangeToCheck = TradingExchanges.Evm;
|
||||
|
||||
public CandleDataHealthCheck(IExchangeService exchangeService)
|
||||
{
|
||||
_exchangeService = exchangeService;
|
||||
}
|
||||
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
// Define timeframes to check with their appropriate start dates and expected freshness
|
||||
var timeframeChecks = new[]
|
||||
{
|
||||
(timeframe: Timeframe.FifteenMinutes, startDate: now.AddDays(-1), maxAgeMins: 30.0),
|
||||
(timeframe: Timeframe.OneHour, startDate: now.AddDays(-3), maxAgeMins: 120.0),
|
||||
(timeframe: Timeframe.OneDay, startDate: now.AddDays(-30), maxAgeMins: 1440.0)
|
||||
};
|
||||
|
||||
var results = new List<Dictionary<string, object>>();
|
||||
var isHealthy = true;
|
||||
|
||||
foreach (var (timeframe, startDate, maxAgeMins) in timeframeChecks)
|
||||
{
|
||||
// Using configured exchange and ticker
|
||||
var candles = await _exchangeService.GetCandlesInflux(
|
||||
_exchangeToCheck,
|
||||
_tickerToCheck,
|
||||
startDate,
|
||||
timeframe);
|
||||
|
||||
var checkResult = new Dictionary<string, object>
|
||||
{
|
||||
["CheckedTicker"] = _tickerToCheck.ToString(),
|
||||
["CheckedTimeframe"] = timeframe.ToString(),
|
||||
["StartDate"] = startDate
|
||||
};
|
||||
|
||||
if (candles == null || !candles.Any())
|
||||
{
|
||||
checkResult["Status"] = "Degraded";
|
||||
checkResult["Message"] = $"No candle data found for {timeframe}";
|
||||
isHealthy = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var latestCandle = candles.OrderByDescending(c => c.Date).FirstOrDefault();
|
||||
var timeDiff = now - latestCandle.Date;
|
||||
|
||||
checkResult["LatestCandleDate"] = latestCandle.Date;
|
||||
checkResult["TimeDifference"] = $"{timeDiff.TotalMinutes:F2} minutes";
|
||||
|
||||
if (timeDiff.TotalMinutes > maxAgeMins)
|
||||
{
|
||||
checkResult["Status"] = "Degraded";
|
||||
checkResult["Message"] = $"Data for {timeframe} is outdated. Latest candle is from {latestCandle.Date:yyyy-MM-dd HH:mm:ss} UTC";
|
||||
isHealthy = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
checkResult["Status"] = "Healthy";
|
||||
checkResult["Message"] = $"Data for {timeframe} is up-to-date. Latest candle is from {latestCandle.Date:yyyy-MM-dd HH:mm:ss} UTC";
|
||||
}
|
||||
}
|
||||
|
||||
results.Add(checkResult);
|
||||
}
|
||||
|
||||
// Combine all results into a summary
|
||||
var resultsDictionary = new Dictionary<string, object>();
|
||||
for (int i = 0; i < results.Count; i++)
|
||||
{
|
||||
resultsDictionary[$"TimeframeCheck_{i+1}"] = results[i];
|
||||
}
|
||||
|
||||
if (isHealthy)
|
||||
{
|
||||
return HealthCheckResult.Healthy(
|
||||
"All candle timeframes are up-to-date",
|
||||
data: resultsDictionary);
|
||||
}
|
||||
else
|
||||
{
|
||||
return HealthCheckResult.Degraded(
|
||||
"One or more candle timeframes are outdated or missing",
|
||||
data: resultsDictionary);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return HealthCheckResult.Unhealthy(
|
||||
"Error checking candle data health",
|
||||
ex,
|
||||
data: new Dictionary<string, object>
|
||||
{
|
||||
["ErrorMessage"] = ex.Message,
|
||||
["ErrorType"] = ex.GetType().Name
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
97
src/Managing.Api/HealthChecks/GmxConnectivityHealthCheck.cs
Normal file
97
src/Managing.Api/HealthChecks/GmxConnectivityHealthCheck.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
|
||||
namespace Managing.Api.HealthChecks
|
||||
{
|
||||
public class GmxConnectivityHealthCheck : IHealthCheck
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly string _gmxCandlesEndpoint = "https://arbitrum-api.gmxinfra.io/prices/candles?tokenSymbol=ETH&period=1m&limit=2";
|
||||
|
||||
public GmxConnectivityHealthCheck(IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_httpClient = httpClientFactory.CreateClient("GmxHealthCheck");
|
||||
}
|
||||
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetAsync(_gmxCandlesEndpoint, cancellationToken);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return HealthCheckResult.Degraded(
|
||||
$"GMX API returned non-success status code: {response.StatusCode}",
|
||||
data: new Dictionary<string, object>
|
||||
{
|
||||
["StatusCode"] = (int)response.StatusCode,
|
||||
["Endpoint"] = _gmxCandlesEndpoint
|
||||
});
|
||||
}
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
|
||||
// Parse the JSON to verify it's valid and has the expected structure
|
||||
using (JsonDocument document = JsonDocument.Parse(content))
|
||||
{
|
||||
var root = document.RootElement;
|
||||
|
||||
// Check if the response has the expected structure
|
||||
if (!root.TryGetProperty("candles", out var candlesElement) ||
|
||||
candlesElement.ValueKind != JsonValueKind.Array ||
|
||||
candlesElement.GetArrayLength() == 0)
|
||||
{
|
||||
return HealthCheckResult.Degraded(
|
||||
"GMX API returned an invalid or empty response",
|
||||
data: new Dictionary<string, object>
|
||||
{
|
||||
["Endpoint"] = _gmxCandlesEndpoint,
|
||||
["Response"] = content
|
||||
});
|
||||
}
|
||||
|
||||
// Get the first candle timestamp to verify freshness
|
||||
var firstCandle = candlesElement[0];
|
||||
if (firstCandle.ValueKind != JsonValueKind.Array || firstCandle.GetArrayLength() < 1)
|
||||
{
|
||||
return HealthCheckResult.Degraded(
|
||||
"GMX API returned invalid candle data format",
|
||||
data: new Dictionary<string, object>
|
||||
{
|
||||
["Endpoint"] = _gmxCandlesEndpoint,
|
||||
["Response"] = content
|
||||
});
|
||||
}
|
||||
|
||||
// Extract timestamp from the first element of the candle array
|
||||
var timestamp = firstCandle[0].GetInt64();
|
||||
var candleTime = DateTimeOffset.FromUnixTimeSeconds(timestamp).UtcDateTime;
|
||||
var timeDiff = DateTime.UtcNow - candleTime;
|
||||
|
||||
return HealthCheckResult.Healthy(
|
||||
"GMX API is responding with valid candle data",
|
||||
data: new Dictionary<string, object>
|
||||
{
|
||||
["Endpoint"] = _gmxCandlesEndpoint,
|
||||
["LatestCandleTimestamp"] = candleTime,
|
||||
["TimeDifference"] = $"{timeDiff.TotalMinutes:F2} minutes",
|
||||
["CandleCount"] = candlesElement.GetArrayLength()
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return HealthCheckResult.Unhealthy(
|
||||
"Failed to connect to GMX API",
|
||||
ex,
|
||||
data: new Dictionary<string, object>
|
||||
{
|
||||
["Endpoint"] = _gmxCandlesEndpoint,
|
||||
["ErrorMessage"] = ex.Message,
|
||||
["ErrorType"] = ex.GetType().Name
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
187
src/Managing.Api/HealthChecks/Web3ProxyHealthCheck.cs
Normal file
187
src/Managing.Api/HealthChecks/Web3ProxyHealthCheck.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
|
||||
namespace Managing.Api.HealthChecks
|
||||
{
|
||||
public class Web3ProxyHealthCheck : IHealthCheck
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly string _web3ProxyUrl;
|
||||
|
||||
public Web3ProxyHealthCheck(IHttpClientFactory httpClientFactory, string web3ProxyUrl)
|
||||
{
|
||||
_httpClient = httpClientFactory.CreateClient("Web3ProxyHealthCheck");
|
||||
_web3ProxyUrl = web3ProxyUrl;
|
||||
}
|
||||
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetAsync($"{_web3ProxyUrl}/health", cancellationToken);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return HealthCheckResult.Degraded(
|
||||
$"Web3Proxy health check failed with status code: {response.StatusCode}",
|
||||
data: new Dictionary<string, object>
|
||||
{
|
||||
["StatusCode"] = (int)response.StatusCode,
|
||||
["Endpoint"] = $"{_web3ProxyUrl}/health"
|
||||
});
|
||||
}
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
|
||||
// Parse the JSON response to extract the detailed data
|
||||
using (JsonDocument document = JsonDocument.Parse(content))
|
||||
{
|
||||
var root = document.RootElement;
|
||||
string status = "healthy";
|
||||
string message = "Web3Proxy is healthy";
|
||||
|
||||
if (root.TryGetProperty("status", out var statusElement))
|
||||
{
|
||||
status = statusElement.GetString();
|
||||
}
|
||||
|
||||
// Extract the detailed data from the Web3Proxy response
|
||||
var data = new Dictionary<string, object>();
|
||||
|
||||
// Parse timestamp if available
|
||||
if (root.TryGetProperty("timestamp", out var timestampElement))
|
||||
{
|
||||
data["timestamp"] = timestampElement.GetString();
|
||||
}
|
||||
|
||||
// Parse version if available
|
||||
if (root.TryGetProperty("version", out var versionElement))
|
||||
{
|
||||
data["version"] = versionElement.GetString();
|
||||
}
|
||||
|
||||
// Parse checks if available
|
||||
if (root.TryGetProperty("checks", out var checksElement))
|
||||
{
|
||||
// Extract Privy check
|
||||
if (checksElement.TryGetProperty("privy", out var privyElement))
|
||||
{
|
||||
var privyData = new Dictionary<string, object>();
|
||||
|
||||
if (privyElement.TryGetProperty("status", out var privyStatusElement))
|
||||
{
|
||||
privyData["status"] = privyStatusElement.GetString();
|
||||
}
|
||||
|
||||
if (privyElement.TryGetProperty("message", out var privyMessageElement))
|
||||
{
|
||||
privyData["message"] = privyMessageElement.GetString();
|
||||
}
|
||||
|
||||
data["privy"] = privyData;
|
||||
}
|
||||
|
||||
// Extract GMX check
|
||||
if (checksElement.TryGetProperty("gmx", out var gmxElement))
|
||||
{
|
||||
var gmxData = new Dictionary<string, object>();
|
||||
|
||||
if (gmxElement.TryGetProperty("status", out var gmxStatusElement))
|
||||
{
|
||||
gmxData["status"] = gmxStatusElement.GetString();
|
||||
}
|
||||
|
||||
if (gmxElement.TryGetProperty("message", out var gmxMessageElement))
|
||||
{
|
||||
gmxData["message"] = gmxMessageElement.GetString();
|
||||
}
|
||||
|
||||
// Extract GMX market data
|
||||
if (gmxElement.TryGetProperty("data", out var gmxDataElement))
|
||||
{
|
||||
if (gmxDataElement.TryGetProperty("marketCount", out var marketCountElement))
|
||||
{
|
||||
gmxData["marketCount"] = marketCountElement.GetInt32();
|
||||
}
|
||||
|
||||
if (gmxDataElement.TryGetProperty("responseTimeMs", out var responseTimeElement))
|
||||
{
|
||||
gmxData["responseTimeMs"] = responseTimeElement.GetInt32();
|
||||
}
|
||||
|
||||
if (gmxDataElement.TryGetProperty("sampleMarkets", out var sampleMarketsElement))
|
||||
{
|
||||
var sampleMarkets = new List<Dictionary<string, string>>();
|
||||
|
||||
for (int i = 0; i < sampleMarketsElement.GetArrayLength(); i++)
|
||||
{
|
||||
var marketElement = sampleMarketsElement[i];
|
||||
var market = new Dictionary<string, string>();
|
||||
|
||||
if (marketElement.TryGetProperty("marketAddress", out var addressElement))
|
||||
{
|
||||
market["marketAddress"] = addressElement.GetString();
|
||||
}
|
||||
|
||||
if (marketElement.TryGetProperty("indexToken", out var indexTokenElement))
|
||||
{
|
||||
market["indexToken"] = indexTokenElement.GetString();
|
||||
}
|
||||
|
||||
if (marketElement.TryGetProperty("longToken", out var longTokenElement))
|
||||
{
|
||||
market["longToken"] = longTokenElement.GetString();
|
||||
}
|
||||
|
||||
if (marketElement.TryGetProperty("shortToken", out var shortTokenElement))
|
||||
{
|
||||
market["shortToken"] = shortTokenElement.GetString();
|
||||
}
|
||||
|
||||
sampleMarkets.Add(market);
|
||||
}
|
||||
|
||||
gmxData["sampleMarkets"] = sampleMarkets;
|
||||
}
|
||||
}
|
||||
|
||||
data["gmx"] = gmxData;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine overall health result based on status
|
||||
if (status.ToLower() == "healthy")
|
||||
{
|
||||
return HealthCheckResult.Healthy(
|
||||
"Web3Proxy is healthy",
|
||||
data: data);
|
||||
}
|
||||
else if (status.ToLower() == "degraded")
|
||||
{
|
||||
return HealthCheckResult.Degraded(
|
||||
"Web3Proxy is degraded",
|
||||
data: data);
|
||||
}
|
||||
else
|
||||
{
|
||||
return HealthCheckResult.Unhealthy(
|
||||
"Web3Proxy is unhealthy",
|
||||
data: data);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return HealthCheckResult.Unhealthy(
|
||||
"Failed to connect to Web3Proxy",
|
||||
ex,
|
||||
data: new Dictionary<string, object>
|
||||
{
|
||||
["Endpoint"] = $"{_web3ProxyUrl}/health",
|
||||
["ErrorMessage"] = ex.Message,
|
||||
["ErrorType"] = ex.GetType().Name
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user