Power service updates

- Upgrade to .NET 8
- Remove ApplicationInsights
- Add OpenTelemetry
This commit is contained in:
2024-01-22 18:54:18 -05:00
parent 911e7fbdb9
commit 2a58b26eb3
16 changed files with 361 additions and 366 deletions

View File

@@ -1,9 +1,8 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 16
VisualStudioVersion = 16.0.28705.295 VisualStudioVersion = 16.0.28705.295
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Service", "Service\Service.csproj", "{914B9DB9-3BCD-4B55-8289-2E59D6CA96BA}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Service", "Service\Service.csproj", "{914B9DB9-3BCD-4B55-8289-2E59D6CA96BA}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@@ -6,29 +6,21 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace ChrisKaczor.HomeMonitor.Power.Service.Controllers namespace ChrisKaczor.HomeMonitor.Power.Service.Controllers;
[Route("[controller]")]
[ApiController]
public class StatusController(Database database) : ControllerBase
{ {
[Route("[controller]")] [HttpGet("recent")]
[ApiController] public async Task<ActionResult<PowerStatus>> GetRecent()
public class StatusController : ControllerBase
{ {
private readonly Database _database; return await database.GetRecentStatus();
}
public StatusController(Database database) [HttpGet("history-grouped")]
{ public async Task<ActionResult<List<PowerStatusGrouped>>> GetHistoryGrouped(DateTimeOffset start, DateTimeOffset end, int bucketMinutes = 2)
_database = database; {
} return (await database.GetStatusHistoryGrouped(start, end, bucketMinutes)).ToList();
[HttpGet("recent")]
public async Task<ActionResult<PowerStatus>> GetRecent()
{
return await _database.GetRecentStatus();
}
[HttpGet("history-grouped")]
public async Task<ActionResult<List<PowerStatusGrouped>>> GetHistoryGrouped(DateTimeOffset start, DateTimeOffset end, int bucketMinutes = 2)
{
return (await _database.GetStatusHistoryGrouped(start, end, bucketMinutes)).ToList();
}
} }
} }

View File

@@ -6,95 +6,89 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace ChrisKaczor.HomeMonitor.Power.Service.Data namespace ChrisKaczor.HomeMonitor.Power.Service.Data;
public class Database(IConfiguration configuration)
{ {
public class Database public void EnsureDatabase()
{ {
private readonly IConfiguration _configuration; var connectionStringBuilder = new SqlConnectionStringBuilder
public Database(IConfiguration configuration)
{ {
_configuration = configuration; DataSource = $"{configuration["Power:Database:Host"]},{configuration["Power:Database:Port"]}",
} UserID = configuration["Power:Database:User"],
Password = configuration["Power:Database:Password"],
InitialCatalog = "master",
TrustServerCertificate = bool.Parse(configuration["Power:Database:TrustServerCertificate"] ?? "false")
};
public void EnsureDatabase() using var connection = new SqlConnection(connectionStringBuilder.ConnectionString);
var command = new SqlCommand { Connection = connection };
connection.Open();
// Check to see if the database exists
command.CommandText = $"SELECT CAST(1 as bit) from sys.databases WHERE name='{configuration["Power:Database:Name"]}'";
var databaseExists = (bool?)command.ExecuteScalar();
// Create database if needed
if (!(databaseExists ?? false))
{ {
var connectionStringBuilder = new SqlConnectionStringBuilder command.CommandText = $"CREATE DATABASE {configuration["Power:Database:Name"]}";
{
DataSource = $"{_configuration["Power:Database:Host"]},{_configuration["Power:Database:Port"]}",
UserID = _configuration["Power:Database:User"],
Password = _configuration["Power:Database:Password"],
InitialCatalog = "master"
};
using var connection = new SqlConnection(connectionStringBuilder.ConnectionString);
var command = new SqlCommand { Connection = connection };
connection.Open();
// Check to see if the database exists
command.CommandText = $"SELECT CAST(1 as bit) from sys.databases WHERE name='{_configuration["Power:Database:Name"]}'";
var databaseExists = (bool?)command.ExecuteScalar();
// Create database if needed
if (!(databaseExists ?? false))
{
command.CommandText = $"CREATE DATABASE {_configuration["Power:Database:Name"]}";
command.ExecuteNonQuery();
}
// Switch to the database now that we're sure it exists
connection.ChangeDatabase(_configuration["Power:Database:Name"]);
var schema = ResourceReader.GetString("ChrisKaczor.HomeMonitor.Power.Service.Data.Resources.Schema.sql");
// Make sure the database is up to date
command.CommandText = schema;
command.ExecuteNonQuery(); command.ExecuteNonQuery();
} }
private SqlConnection CreateConnection() // Switch to the database now that we're sure it exists
connection.ChangeDatabase(configuration["Power:Database:Name"]!);
var schema = ResourceReader.GetString("ChrisKaczor.HomeMonitor.Power.Service.Data.Resources.Schema.sql");
// Make sure the database is up-to-date
command.CommandText = schema;
command.ExecuteNonQuery();
}
private SqlConnection CreateConnection()
{
var connectionStringBuilder = new SqlConnectionStringBuilder
{ {
var connectionStringBuilder = new SqlConnectionStringBuilder DataSource = $"{configuration["Power:Database:Host"]},{configuration["Power:Database:Port"]}",
{ UserID = configuration["Power:Database:User"],
DataSource = $"{_configuration["Power:Database:Host"]},{_configuration["Power:Database:Port"]}", Password = configuration["Power:Database:Password"],
UserID = _configuration["Power:Database:User"], InitialCatalog = configuration["Power:Database:Name"],
Password = _configuration["Power:Database:Password"], TrustServerCertificate = bool.Parse(configuration["Power:Database:TrustServerCertificate"] ?? "false")
InitialCatalog = _configuration["Power:Database:Name"] };
};
var connection = new SqlConnection(connectionStringBuilder.ConnectionString); var connection = new SqlConnection(connectionStringBuilder.ConnectionString);
connection.Open(); connection.Open();
return connection; return connection;
} }
public void StorePowerData(PowerStatus powerStatus) public void StorePowerData(PowerStatus powerStatus)
{ {
using var connection = CreateConnection(); using var connection = CreateConnection();
var query = ResourceReader.GetString("ChrisKaczor.HomeMonitor.Power.Service.Data.Resources.CreateStatus.sql"); var query = ResourceReader.GetString("ChrisKaczor.HomeMonitor.Power.Service.Data.Resources.CreateStatus.sql");
connection.Query(query, powerStatus); connection.Query(query, powerStatus);
} }
public async Task<PowerStatus> GetRecentStatus() public async Task<PowerStatus> GetRecentStatus()
{ {
await using var connection = CreateConnection(); await using var connection = CreateConnection();
var query = ResourceReader.GetString("ChrisKaczor.HomeMonitor.Power.Service.Data.Resources.GetRecentStatus.sql"); var query = ResourceReader.GetString("ChrisKaczor.HomeMonitor.Power.Service.Data.Resources.GetRecentStatus.sql");
return await connection.QueryFirstOrDefaultAsync<PowerStatus>(query); return await connection.QueryFirstOrDefaultAsync<PowerStatus>(query);
} }
public async Task<IEnumerable<PowerStatusGrouped>> GetStatusHistoryGrouped(DateTimeOffset start, DateTimeOffset end, int bucketMinutes) public async Task<IEnumerable<PowerStatusGrouped>> GetStatusHistoryGrouped(DateTimeOffset start, DateTimeOffset end, int bucketMinutes)
{ {
await using var connection = CreateConnection(); await using var connection = CreateConnection();
var query = ResourceReader.GetString("ChrisKaczor.HomeMonitor.Power.Service.Data.Resources.GetStatusHistoryGrouped.sql"); var query = ResourceReader.GetString("ChrisKaczor.HomeMonitor.Power.Service.Data.Resources.GetStatusHistoryGrouped.sql");
return await connection.QueryAsync<PowerStatusGrouped>(query, new { Start = start, End = end, BucketMinutes = bucketMinutes }); return await connection.QueryAsync<PowerStatusGrouped>(query, new { Start = start, End = end, BucketMinutes = bucketMinutes });
}
} }
} }

View File

@@ -1,8 +1,8 @@
FROM mcr.microsoft.com/dotnet/aspnet:5.0-alpine AS base FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:5.0-alpine AS build FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
WORKDIR /src WORKDIR /src
COPY ["./Service.csproj", "./"] COPY ["./Service.csproj", "./"]
RUN dotnet restore "Service.csproj" RUN dotnet restore "Service.csproj"

View File

@@ -1,30 +1,29 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace ChrisKaczor.HomeMonitor.Power.Service.Models namespace ChrisKaczor.HomeMonitor.Power.Service.Models;
[PublicAPI]
public class PowerChannel
{ {
[PublicAPI] [JsonPropertyName("type")]
public class PowerChannel public string Type { get; set; }
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("ch")] [JsonPropertyName("ch")]
public long ChannelNumber { get; set; } public long ChannelNumber { get; set; }
[JsonPropertyName("eImp_Ws")] [JsonPropertyName("eImp_Ws")]
public long ImportedEnergy { get; set; } public long ImportedEnergy { get; set; }
[JsonPropertyName("eExp_Ws")] [JsonPropertyName("eExp_Ws")]
public long ExportedEnergy { get; set; } public long ExportedEnergy { get; set; }
[JsonPropertyName("p_W")] [JsonPropertyName("p_W")]
public long RealPower { get; set; } public long RealPower { get; set; }
[JsonPropertyName("q_VAR")] [JsonPropertyName("q_VAR")]
public long ReactivePower { get; set; } public long ReactivePower { get; set; }
[JsonPropertyName("v_V")] [JsonPropertyName("v_V")]
public double Voltage { get; set; } public double Voltage { get; set; }
}
} }

View File

@@ -3,21 +3,20 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace ChrisKaczor.HomeMonitor.Power.Service.Models namespace ChrisKaczor.HomeMonitor.Power.Service.Models;
[PublicAPI]
public class PowerSample
{ {
[PublicAPI] [JsonPropertyName("sensorId")]
public class PowerSample public string SensorId { get; set; }
{
[JsonPropertyName("sensorId")]
public string SensorId { get; set; }
[JsonPropertyName("timestamp")] [JsonPropertyName("timestamp")]
public DateTimeOffset Timestamp { get; set; } public DateTimeOffset Timestamp { get; set; }
[JsonPropertyName("channels")] [JsonPropertyName("channels")]
public PowerChannel[] Channels { get; set; } public PowerChannel[] Channels { get; set; }
[JsonPropertyName("cts")] [JsonPropertyName("cts")]
public Dictionary<string, double>[] CurrentTransformers { get; set; } public Dictionary<string, double>[] CurrentTransformers { get; set; }
}
} }

View File

@@ -1,13 +1,12 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using System; using System;
namespace ChrisKaczor.HomeMonitor.Power.Service.Models namespace ChrisKaczor.HomeMonitor.Power.Service.Models;
[PublicAPI]
public class PowerStatus
{ {
[PublicAPI] public DateTimeOffset Timestamp { get; set; } = DateTimeOffset.UtcNow;
public class PowerStatus public long Generation { get; set; }
{ public long Consumption { get; set; }
public DateTimeOffset Timestamp { get; set; } = DateTimeOffset.UtcNow;
public long Generation { get; set; }
public long Consumption { get; set; }
}
} }

View File

@@ -1,13 +1,12 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using System; using System;
namespace ChrisKaczor.HomeMonitor.Power.Service.Models namespace ChrisKaczor.HomeMonitor.Power.Service.Models;
[PublicAPI]
public class PowerStatusGrouped
{ {
[PublicAPI] public DateTimeOffset Bucket { get; set; }
public class PowerStatusGrouped public long AverageGeneration { get; set; }
{ public long AverageConsumption { get; set; }
public DateTimeOffset Bucket { get; set; }
public long AverageGeneration { get; set; }
public long AverageConsumption { get; set; }
}
} }

View File

@@ -1,102 +1,94 @@
using ChrisKaczor.HomeMonitor.Power.Service.Data; using ChrisKaczor.HomeMonitor.Power.Service.Data;
using ChrisKaczor.HomeMonitor.Power.Service.Models; using ChrisKaczor.HomeMonitor.Power.Service.Models;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.ApplicationInsights;
using Microsoft.AspNetCore.SignalR.Client; using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using RestSharp; using RestSharp;
using System; using System;
using System.Diagnostics;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace ChrisKaczor.HomeMonitor.Power.Service namespace ChrisKaczor.HomeMonitor.Power.Service;
[UsedImplicitly]
public class PowerReader(IConfiguration configuration, Database database, ILogger<PowerReader> logger) : IHostedService
{ {
[UsedImplicitly] private readonly ActivitySource _activitySource = new(nameof(PowerReader));
public class PowerReader : IHostedService
private HubConnection _hubConnection;
private Timer _readTimer;
public Task StartAsync(CancellationToken cancellationToken)
{ {
private readonly IConfiguration _configuration; logger.LogInformation($"{nameof(PowerReader)} - Start");
private readonly Database _database;
private readonly TelemetryClient _telemetryClient;
private HubConnection _hubConnection; _readTimer = new Timer(GetCurrentSample, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
private Timer _readTimer;
public PowerReader(IConfiguration configuration, Database database, TelemetryClient telemetryClient) if (!string.IsNullOrEmpty(configuration["Hub:Power"]))
_hubConnection = new HubConnectionBuilder().WithUrl(configuration["Hub:Power"]).Build();
return Task.CompletedTask;
}
private void GetCurrentSample(object state)
{
using var activity = _activitySource.StartActivity();
try
{ {
_configuration = configuration; var client = new RestClient(configuration["Power:Host"]!);
_database = database;
_telemetryClient = telemetryClient; var request = new RestRequest("current-sample");
request.AddHeader("Authorization", configuration["Power:AuthorizationHeader"]!);
var response = client.Execute(request);
var content = response.Content!;
logger.LogInformation("API response: {content}", content);
var sample = JsonSerializer.Deserialize<PowerSample>(content);
var generation = Array.Find(sample.Channels, c => c.Type == "GENERATION");
var consumption = Array.Find(sample.Channels, c => c.Type == "CONSUMPTION");
if (generation == null || consumption == null)
return;
var status = new PowerStatus { Generation = generation.RealPower, Consumption = consumption.RealPower };
database.StorePowerData(status);
var json = JsonSerializer.Serialize(status);
logger.LogInformation("Output message: {json}", json);
if (_hubConnection == null)
return;
if (_hubConnection.State == HubConnectionState.Disconnected)
_hubConnection.StartAsync().Wait();
_hubConnection.InvokeAsync("SendLatestSample", json).Wait();
} }
catch (Exception exception)
public Task StartAsync(CancellationToken cancellationToken)
{ {
_telemetryClient.TrackTrace($"{nameof(PowerReader)} - Start"); logger.LogError(exception, "Exception");
_readTimer = new Timer(OnTimer, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
if (!string.IsNullOrEmpty(_configuration["Hub:Power"]))
_hubConnection = new HubConnectionBuilder().WithUrl(_configuration["Hub:Power"]).Build();
return Task.CompletedTask;
}
private void OnTimer(object state)
{
try
{
var client = new RestClient(_configuration["Power:Host"]);
var request = new RestRequest("current-sample", Method.GET);
request.AddHeader("Authorization", _configuration["Power:AuthorizationHeader"]);
var response = client.Execute(request);
var sample = JsonSerializer.Deserialize<PowerSample>(response.Content);
var generation = Array.Find(sample.Channels, c => c.Type == "GENERATION");
var consumption = Array.Find(sample.Channels, c => c.Type == "CONSUMPTION");
if (generation == null || consumption == null)
return;
var status = new PowerStatus { Generation = generation.RealPower, Consumption = consumption.RealPower };
_database.StorePowerData(status);
var json = JsonSerializer.Serialize(status);
Console.WriteLine(json);
if (_hubConnection == null)
return;
if (_hubConnection.State == HubConnectionState.Disconnected)
_hubConnection.StartAsync().Wait();
_hubConnection.InvokeAsync("SendLatestSample", json).Wait();
}
catch (Exception exception)
{
WriteLog($"Exception: {exception}");
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_telemetryClient.TrackTrace($"{nameof(PowerReader)} - Stop");
_readTimer.Dispose();
_hubConnection?.StopAsync(cancellationToken).Wait(cancellationToken);
return Task.CompletedTask;
}
private static void WriteLog(string message)
{
Console.WriteLine(message);
} }
} }
public Task StopAsync(CancellationToken cancellationToken)
{
logger.LogInformation($"{nameof(PowerReader)} - Stop");
_readTimer.Dispose();
_hubConnection?.StopAsync(cancellationToken).Wait(cancellationToken);
return Task.CompletedTask;
}
} }

View File

@@ -1,19 +1,127 @@
using Microsoft.AspNetCore; using ChrisKaczor.HomeMonitor.Power.Service.Data;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using OpenTelemetry.Exporter;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System;
using System.IO.Compression;
using System.Reflection;
namespace ChrisKaczor.HomeMonitor.Power.Service namespace ChrisKaczor.HomeMonitor.Power.Service;
public static class Program
{ {
public static class Program public static void Main(string[] args)
{ {
public static void Main(string[] args) var builder = WebApplication.CreateBuilder(args);
{
CreateWebHostBuilder(args).Build().Run();
}
private static IWebHostBuilder CreateWebHostBuilder(string[] args) builder.Configuration.AddEnvironmentVariables();
builder.Services.AddControllers();
// ---
var openTelemetry = builder.Services.AddOpenTelemetry();
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
var name = Assembly.GetExecutingAssembly().GetName().Name;
openTelemetry.ConfigureResource(resource => resource.AddService(name!));
openTelemetry.WithMetrics(meterProviderBuilder => meterProviderBuilder
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddProcessInstrumentation()
.AddMeter("Microsoft.AspNetCore.Hosting")
.AddMeter("Microsoft.AspNetCore.Server.Kestrel"));
openTelemetry.WithTracing(tracerProviderBuilder =>
{ {
return WebHost.CreateDefaultBuilder(args).ConfigureAppConfiguration((_, config) => config.AddEnvironmentVariables()).UseStartup<Startup>(); tracerProviderBuilder.AddAspNetCoreInstrumentation(instrumentationOptions => instrumentationOptions.RecordException = true);
}
tracerProviderBuilder.AddHttpClientInstrumentation(instrumentationOptions => instrumentationOptions.RecordException = true);
tracerProviderBuilder.AddSqlClientInstrumentation(o =>
{
o.RecordException = true;
o.SetDbStatementForText = true;
});
tracerProviderBuilder.AddSource(nameof(PowerReader));
if (builder.Environment.IsDevelopment())
tracerProviderBuilder.AddConsoleExporter();
tracerProviderBuilder.SetErrorStatusOnException();
tracerProviderBuilder.AddOtlpExporter(exporterOptions =>
{
exporterOptions.Endpoint = new Uri(builder.Configuration["Telemetry:Endpoint"]!);
exporterOptions.Protocol = OtlpExportProtocol.Grpc;
});
});
builder.Services.AddLogging((loggingBuilder) =>
{
loggingBuilder.SetMinimumLevel(LogLevel.Information);
loggingBuilder.AddOpenTelemetry(options =>
{
if (builder.Environment.IsDevelopment())
options.AddConsoleExporter();
options.AddOtlpExporter(exporterOptions =>
{
exporterOptions.Endpoint = new Uri(builder.Configuration["Telemetry:Endpoint"]!);
exporterOptions.Protocol = OtlpExportProtocol.Grpc;
});
}
);
});
builder.Services.AddTransient<Database>();
builder.Services.AddHostedService<PowerReader>();
builder.Services.Configure<GzipCompressionProviderOptions>(options => options.Level = CompressionLevel.Optimal);
builder.Services.AddResponseCompression(options =>
{
options.Providers.Add<GzipCompressionProvider>();
options.EnableForHttps = true;
});
builder.Services.AddCors(o => o.AddPolicy("CorsPolicy", corsPolicyBuilder => corsPolicyBuilder.AllowAnyMethod().AllowAnyHeader().AllowCredentials().WithOrigins("http://localhost:4200")));
builder.Services.AddMvc();
// ---
var app = builder.Build();
if (builder.Environment.IsDevelopment())
app.UseDeveloperExceptionPage();
var database = app.Services.GetRequiredService<Database>();
database.EnsureDatabase();
app.UseCors("CorsPolicy");
app.UseResponseCompression();
app.UseRouting();
app.UseAuthorization();
app.MapControllers();
app.Run();
} }
} }

View File

@@ -2,21 +2,20 @@
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
namespace ChrisKaczor.HomeMonitor.Power.Service namespace ChrisKaczor.HomeMonitor.Power.Service;
public static class ResourceReader
{ {
public static class ResourceReader public static string GetString(string resourceName)
{ {
public static string GetString(string resourceName) var assembly = Assembly.GetExecutingAssembly();
{ using var stream = assembly.GetManifestResourceStream(resourceName);
var assembly = Assembly.GetExecutingAssembly();
using var stream = assembly.GetManifestResourceStream(resourceName);
if (stream == null) if (stream == null)
throw new Exception($"Resource {resourceName} not found in {assembly.FullName}. Valid resources are: {string.Join(", ", assembly.GetManifestResourceNames())}."); throw new Exception($"Resource {resourceName} not found in {assembly.FullName}. Valid resources are: {string.Join(", ", assembly.GetManifestResourceNames())}.");
using var reader = new StreamReader(stream); using var reader = new StreamReader(stream);
return reader.ReadToEnd(); return reader.ReadToEnd();
}
} }
} }

View File

@@ -1,36 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net5.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel> <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
<AssemblyName>ChrisKaczor.HomeMonitor.Power.Service</AssemblyName> <AssemblyName>ChrisKaczor.HomeMonitor.Power.Service</AssemblyName>
<RootNamespace>ChrisKaczor.HomeMonitor.Power.Service</RootNamespace> <RootNamespace>ChrisKaczor.HomeMonitor.Power.Service</RootNamespace>
<CodeAnalysisRuleSet>../../ChrisKaczor.ruleset</CodeAnalysisRuleSet>
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute> <GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Remove="C:\Users\chris\.nuget\packages\opentelemetry.autoinstrumentation\1.3.0\contentFiles\any\any\instrument.cmd" />
<None Remove="C:\Users\chris\.nuget\packages\opentelemetry.autoinstrumentation\1.3.0\contentFiles\any\any\instrument.sh" />
<None Remove="Data\Resources\CreateStatus.sql" /> <None Remove="Data\Resources\CreateStatus.sql" />
<None Remove="Data\Resources\GetStatusHistoryGrouped.sql" /> <None Remove="Data\Resources\GetStatusHistoryGrouped.sql" />
<None Remove="Data\Resources\Schema.sql" /> <None Remove="Data\Resources\Schema.sql" />
<None Remove="Data\Resources\GetRecentStatus.sql" /> <None Remove="Data\Resources\GetRecentStatus.sql" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Data\Resources\CreateStatus.sql" /> <EmbeddedResource Include="Data\Resources\CreateStatus.sql" />
<EmbeddedResource Include="Data\Resources\GetStatusHistoryGrouped.sql" /> <EmbeddedResource Include="Data\Resources\GetStatusHistoryGrouped.sql" />
<EmbeddedResource Include="Data\Resources\Schema.sql" /> <EmbeddedResource Include="Data\Resources\Schema.sql" />
<EmbeddedResource Include="Data\Resources\GetRecentStatus.sql" /> <EmbeddedResource Include="Data\Resources\GetRecentStatus.sql" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Dapper" Version="2.0.90" /> <PackageReference Include="Dapper" Version="2.1.28" />
<PackageReference Include="JetBrains.Annotations" Version="2021.1.0" /> <PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="Microsoft.ApplicationInsights" Version="2.17.0" /> <PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.4" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.17.0" /> <PackageReference Include="OpenTelemetry.AutoInstrumentation" Version="1.3.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.6" /> <PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.7.0" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="2.1.3" /> <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.7.0" />
<PackageReference Include="RestSharp" Version="106.11.7" /> <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.7.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.7.0" />
<PackageReference Include="RestSharp" Version="110.2.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,69 +0,0 @@
using ChrisKaczor.HomeMonitor.Power.Service.Data;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.IO.Compression;
using System.Threading;
namespace ChrisKaczor.HomeMonitor.Power.Service
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ITelemetryInitializer, TelemetryInitializer>();
services.AddApplicationInsightsTelemetry(options =>
{
options.EnableDependencyTrackingTelemetryModule = false;
});
services.AddTransient<Database>();
services.AddHostedService<PowerReader>();
services.Configure<GzipCompressionProviderOptions>(options => options.Level = CompressionLevel.Optimal);
services.AddResponseCompression(options =>
{
options.Providers.Add<GzipCompressionProvider>();
options.EnableForHttps = true;
});
services.AddCors(o => o.AddPolicy("CorsPolicy", builder => builder.AllowAnyMethod().AllowAnyHeader().AllowCredentials().WithOrigins("http://localhost:4200")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
}
public void Configure(IApplicationBuilder applicationBuilder, IWebHostEnvironment environment, IHostApplicationLifetime hostApplicationLifetime)
{
if (environment.IsDevelopment())
applicationBuilder.UseDeveloperExceptionPage();
hostApplicationLifetime.ApplicationStopping.Register(() =>
{
var telemetryClient = applicationBuilder.ApplicationServices.GetRequiredService<TelemetryClient>();
telemetryClient.Flush();
Thread.Sleep(5000);
});
var database = applicationBuilder.ApplicationServices.GetRequiredService<Database>();
database.EnsureDatabase();
applicationBuilder.UseCors("CorsPolicy");
applicationBuilder.UseResponseCompression();
applicationBuilder.UseRouting();
applicationBuilder.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute());
}
}
}

View File

@@ -1,14 +0,0 @@
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.Extensibility;
using System.Reflection;
namespace ChrisKaczor.HomeMonitor.Power.Service
{
public class TelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
telemetry.Context.Cloud.RoleName = Assembly.GetEntryAssembly()?.GetName().Name;
}
}
}

View File

@@ -1,16 +1,20 @@
{ {
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Warning" "Default": "Information"
}
},
"Power": {
"Database": {
"Name": "Power",
"Port": 1434,
"TrustServerCertificate": true
}
},
"Hub": {
"Power": ""
},
"Telemetry": {
"Endpoint": "http://signoz-otel-collector.platform:4317/"
} }
},
"Power": {
"Database": {
"Name": "Power",
"Port": 1434
}
},
"Hub": {
"Power": "http://hub-server/power"
}
} }

View File

@@ -94,11 +94,6 @@ spec:
securityContext: securityContext:
privileged: true privileged: true
env: env:
- name: ApplicationInsights__ConnectionString
valueFrom:
secretKeyRef:
name: telemetry
key: key
- name: Power__Database__Host - name: Power__Database__Host
value: power-database value: power-database
- name: Power__Database__User - name: Power__Database__User