Skip to content

Commit

Permalink
Fix style suggestions and add some error handling logs
Browse files Browse the repository at this point in the history
  • Loading branch information
DrEsteban committed Nov 20, 2024
1 parent 705a5e1 commit f29f099
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 47 deletions.
3 changes: 3 additions & 0 deletions src/Exceptions/MetricRequestFailedException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ public class MetricRequestFailedException : Exception
{
public MetricRequestFailedException(string message) : base(message)
{ }

public MetricRequestFailedException(string message, Exception innerException) : base(message, innerException)
{ }
}
8 changes: 5 additions & 3 deletions src/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,11 @@
})
.ConfigurePrimaryHttpMessageHandler(_ =>
{
var handler = new HttpClientHandler();
// Tesla Gateway serves a self-signed cert
handler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true;
var handler = new HttpClientHandler
{
// Tesla Gateway serves a self-signed cert
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
};
return handler;
})
.UseHttpClientMetrics()
Expand Down
18 changes: 5 additions & 13 deletions src/Services/EnphaseMetricsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,18 @@ namespace SolarGateway_PrometheusProxy.Services;
/// This class was built by doing limited reverse engineering of a friend's Enphase gateway.
/// There may be more metrics available with additional investigation.
/// </remarks>
public class EnphaseMetricsService : MetricsServiceBase
public class EnphaseMetricsService(
HttpClient httpClient,
ILogger<EnphaseMetricsService> logger) : MetricsServiceBase(httpClient, logger)
{
public EnphaseMetricsService(
HttpClient httpClient,
ILogger<EnphaseMetricsService> logger)
: base(httpClient, logger)
{ }

protected override string MetricCategory => "enphase";

public override async Task CollectMetricsAsync(CollectorRegistry collectorRegistry, CancellationToken cancellationToken = default)
{
var sw = Stopwatch.StartNew();

using var metricsDocument = await base.CallMetricEndpointAsync("/production.json", () => null, cancellationToken);
if (metricsDocument == null)
{
throw new MetricRequestFailedException($"Failed to pull metric endpoint endpoints on Enphase gateway");
}

using var metricsDocument = (await base.CallMetricEndpointAsync("/production.json", () => null, cancellationToken))
?? throw new MetricRequestFailedException($"Failed to pull metric endpoint endpoints on Enphase gateway");
var production = metricsDocument.RootElement
.GetProperty("production")
.EnumerateArray()
Expand Down
34 changes: 21 additions & 13 deletions src/Services/MetricsServiceBase.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Net.Http.Headers;
using System.Text.Json;
using Prometheus;
using SolarGateway_PrometheusProxy.Exceptions;
using SolarGateway_PrometheusProxy.Models;

namespace SolarGateway_PrometheusProxy.Services;
Expand All @@ -22,8 +23,8 @@ public MetricsServiceBase(HttpClient client, ILogger logger)

protected Gauge.Child CreateGauge(CollectorRegistry registry, string subCategory, string metric, params KeyValuePair<string, string>[] labels)
{
var labelKeys = labels.Select(l => l.Key).Concat(new[] { $"{this.GetType().Name}Host" }).ToArray();
var labelValues = labels.Select(l => l.Value).Concat(new[] { _client.BaseAddress!.Host }).ToArray();
var labelKeys = labels.Select(l => l.Key).Concat([$"{this.GetType().Name}Host"]).ToArray();
var labelValues = labels.Select(l => l.Value).Concat([_client.BaseAddress!.Host]).ToArray();
return Metrics.WithCustomRegistry(registry).CreateGauge($"solarapiproxy_{MetricCategory}_{subCategory}_{metric}", metric, labelKeys)
.WithLabels(labelValues);
}
Expand All @@ -47,21 +48,28 @@ protected void SetRequestDurationMetric(CollectorRegistry registry, bool loginCa

protected async Task<JsonDocument?> CallMetricEndpointAsync(string path, Func<AuthenticationHeaderValue?> authenticationCallback, CancellationToken cancellationToken)
{
using var request = new HttpRequestMessage(HttpMethod.Get, path);
var authHeader = authenticationCallback();
if (authHeader != null)
try
{
request.Headers.Authorization = authHeader;
}
using var request = new HttpRequestMessage(HttpMethod.Get, path);
var authHeader = authenticationCallback();
if (authHeader != null)
{
request.Headers.Authorization = authHeader;
}

using var response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);

using var response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
if (!response.IsSuccessStatusCode)
{
_logger.LogError($"Got {response.StatusCode} calling '{path}': {await response.Content.ReadAsStringAsync()}");
return null;
}

if (!response.IsSuccessStatusCode)
return await response.Content.ReadFromJsonAsync<JsonDocument>(JsonModelContext.Default.JsonDocument, cancellationToken);
}
catch (Exception ex)
{
_logger.LogError($"Got {response.StatusCode} calling '{path}': {await response.Content.ReadAsStringAsync()}");
return null;
throw new MetricRequestFailedException(ex.Message, ex);
}

return await response.Content.ReadFromJsonAsync<JsonDocument>(JsonModelContext.Default.JsonDocument, cancellationToken);
}
}
33 changes: 15 additions & 18 deletions src/Services/TeslaGatewayMetricsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,16 @@ namespace SolarGateway_PrometheusProxy.Services;
/// <summary>
/// Provides metrics from a Tesla Gateway and saves them to a Prometheus <see cref="CollectorRegistry"/>.
/// </summary>
public partial class TeslaGatewayMetricsService : MetricsServiceBase
public partial class TeslaGatewayMetricsService(
HttpClient httpClient,
IOptions<TeslaConfiguration> configuration,
IOptions<TeslaLoginRequest> loginRequest,
ILogger<TeslaGatewayMetricsService> logger,
IMemoryCache cache) : MetricsServiceBase(httpClient, logger)
{
private readonly TeslaLoginRequest _loginRequest;
private readonly IMemoryCache _cache;
private readonly TimeSpan _loginCacheLength;

public TeslaGatewayMetricsService(
HttpClient httpClient,
IOptions<TeslaConfiguration> configuration,
IOptions<TeslaLoginRequest> loginRequest,
ILogger<TeslaGatewayMetricsService> logger,
IMemoryCache cache)
: base(httpClient, logger)
{
_loginRequest = loginRequest.Value;
_cache = cache;
_loginCacheLength = TimeSpan.FromMinutes(configuration.Value.LoginCacheMinutes);
}
private readonly TeslaLoginRequest _loginRequest = loginRequest.Value;
private readonly IMemoryCache _cache = cache;
private readonly TimeSpan _loginCacheLength = TimeSpan.FromMinutes(configuration.Value.LoginCacheMinutes);

protected override string MetricCategory => "tesla_gateway";

Expand Down Expand Up @@ -89,6 +81,7 @@ private async Task<bool> PullMeterAggregates(CollectorRegistry registry, TeslaLo
using var metricsDocument = await base.CallMetricEndpointAsync("/api/meters/aggregates", loginResponse.ToAuthenticationHeader, cancellationToken);
if (metricsDocument is null)
{
this._logger.LogError("API Meter aggregates document is null");
return false;
}

Expand Down Expand Up @@ -124,6 +117,7 @@ private async Task<bool> PullPowerwallPercentage(CollectorRegistry registry, Tes
using var metricsDocument = await base.CallMetricEndpointAsync("/api/system_status/soe", loginResponse.ToAuthenticationHeader, cancellationToken);
if (metricsDocument is null)
{
this._logger.LogError("API Powerwall percentage document is null");
return false;
}

Expand All @@ -136,6 +130,7 @@ private async Task<bool> PullSiteInfo(CollectorRegistry registry, TeslaLoginResp
using var metricsDocument = await base.CallMetricEndpointAsync("/api/site_info", loginResponse.ToAuthenticationHeader, cancellationToken);
if (metricsDocument is null)
{
this._logger.LogError("Site info document is null");
return false;
}

Expand All @@ -156,6 +151,7 @@ private async Task<bool> PullStatus(CollectorRegistry registry, TeslaLoginRespon
using var metricsDocument = await base.CallMetricEndpointAsync("/api/status", loginResponse.ToAuthenticationHeader, cancellationToken);
if (metricsDocument is null)
{
this._logger.LogError("API Status document is null");
return false;
}

Expand All @@ -182,13 +178,14 @@ private async Task<bool> PullOperation(CollectorRegistry registry, TeslaLoginRes
using var metricsDocument = await base.CallMetricEndpointAsync("/api/operation", loginResponse.ToAuthenticationHeader, cancellationToken);
if (metricsDocument is null)
{
this._logger.LogError("API Operation document is null");
return false;
}

base.CreateGauge(registry, "operation", "backup_reserve_percent").Set(metricsDocument.RootElement.GetProperty("backup_reserve_percent").GetDouble());

string? realMode = metricsDocument.RootElement.GetProperty("real_mode").GetString();
Func<string, Gauge.Child> GetModeGauge = (mode) => base.CreateGauge(registry, "operation", "mode", KeyValuePair.Create("mode", mode));
Gauge.Child GetModeGauge(string mode) => base.CreateGauge(registry, "operation", "mode", KeyValuePair.Create("mode", mode));

const string selfConsumption = "self_consumption", autonomous = "autonomous", backup = "backup";
GetModeGauge(selfConsumption).Set(realMode == selfConsumption ? 1 : 0);
Expand Down

0 comments on commit f29f099

Please sign in to comment.