Skip to content

Commit a4ef97a

Browse files
adamintAdam Ratzmanmitchdenny
authored
Make resource HealthStatus computed from HealthReports (#6368)
* Make resource HealthStatus computed from HealthReports * Update health reports when they have changed but the status has not --------- Co-authored-by: Adam Ratzman <[email protected]> Co-authored-by: Mitch Denny <[email protected]>
1 parent 69cabab commit a4ef97a

36 files changed

+248
-214
lines changed

playground/HealthChecks/HealthChecksSandbox.AppHost/Program.cs

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using Aspire.Hosting.Lifecycle;
5+
using Microsoft.Extensions.DependencyInjection;
56
using Microsoft.Extensions.Diagnostics.HealthChecks;
67

78
var builder = DistributedApplication.CreateBuilder(args);
89

910
builder.Services.TryAddLifecycleHook<TestResourceLifecycleHook>();
1011

1112
AddTestResource("healthy", HealthStatus.Healthy, "I'm fine, thanks for asking.");
12-
AddTestResource("unhealthy", HealthStatus.Unhealthy, "I can't do that, Dave.", exception: GetException("Feeling unhealthy."));
13-
AddTestResource("degraded", HealthStatus.Degraded, "Had better days.", exception: GetException("Feeling degraded."));
13+
AddTestResource("unhealthy", HealthStatus.Unhealthy, "I can't do that, Dave.", exceptionMessage: "Feeling unhealthy.");
14+
AddTestResource("degraded", HealthStatus.Degraded, "Had better days.", exceptionMessage: "Feeling degraded.");
1415

1516
#if !SKIP_DASHBOARD_REFERENCE
1617
// This project is only added in playground projects to support development/debugging
@@ -24,30 +25,37 @@
2425

2526
builder.Build().Run();
2627

27-
static string GetException(string message)
28+
void AddTestResource(string name, HealthStatus status, string? description = null, string? exceptionMessage = null)
2829
{
29-
try
30-
{
31-
throw new InvalidOperationException(message);
32-
}
33-
catch (InvalidOperationException ex)
34-
{
35-
return ex.ToString();
36-
}
37-
}
30+
var hasHealthyAfterFirstRunCheckRun = false;
31+
builder.Services.AddHealthChecks()
32+
.AddCheck(
33+
$"{name}_check",
34+
() => new HealthCheckResult(status, description, new InvalidOperationException(exceptionMessage))
35+
)
36+
.AddCheck($"{name}_resource_healthy_after_first_run_check", () =>
37+
{
38+
if (!hasHealthyAfterFirstRunCheckRun)
39+
{
40+
hasHealthyAfterFirstRunCheckRun = true;
41+
return new HealthCheckResult(HealthStatus.Unhealthy, "Initial failure state.");
42+
}
3843

39-
IResourceBuilder<TestResource> AddTestResource(string name, HealthStatus status, string? description = null, string? exception = null)
40-
{
41-
return builder
44+
return new HealthCheckResult(HealthStatus.Healthy, "Healthy beginning second health check run.");
45+
});
46+
47+
builder
4248
.AddResource(new TestResource(name))
49+
.WithHealthCheck($"{name}_check")
50+
.WithHealthCheck($"{name}_resource_healthy_after_first_run_check")
4351
.WithInitialState(new()
4452
{
4553
ResourceType = "Test Resource",
4654
State = "Starting",
4755
Properties = [],
48-
HealthReports = [new HealthReportSnapshot($"{name}_check", status, description, exception)]
4956
})
5057
.ExcludeFromManifest();
58+
return;
5159
}
5260

5361
internal sealed class TestResource(string name) : Resource(name);

src/Aspire.Dashboard/Model/ResourceStateViewModel.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,6 @@ internal static ResourceStateViewModel GetStateViewModel(ResourceViewModel resou
6262
icon = new Icons.Filled.Size16.Circle();
6363
color = Color.Info;
6464
}
65-
else if (resource.HealthStatus is null)
66-
{
67-
// If we are waiting for a health check, show a progress bar and consider the resource unhealthy
68-
icon = new Icons.Filled.Size16.CheckmarkCircleWarning();
69-
color = Color.Warning;
70-
}
7165
else if (resource.HealthStatus is not HealthStatus.Healthy)
7266
{
7367
icon = new Icons.Filled.Size16.CheckmarkCircleWarning();

src/Aspire.Dashboard/Model/ResourceViewModel.cs

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ namespace Aspire.Dashboard.Model;
2020
[DebuggerDisplay("Name = {Name}, ResourceType = {ResourceType}, State = {State}, Properties = {Properties.Count}")]
2121
public sealed class ResourceViewModel
2222
{
23+
private readonly ImmutableArray<HealthReportViewModel> _healthReports = [];
24+
private readonly KnownResourceState? _knownState;
25+
2326
public required string Name { get; init; }
2427
public required string ResourceType { get; init; }
2528
public required string DisplayName { get; init; }
@@ -35,16 +38,50 @@ public sealed class ResourceViewModel
3538
public required FrozenDictionary<string, ResourcePropertyViewModel> Properties { get; init; }
3639
public required ImmutableArray<CommandViewModel> Commands { get; init; }
3740
/// <summary>The health status of the resource. <see langword="null"/> indicates that health status is expected but not yet available.</summary>
38-
public required HealthStatus? HealthStatus { get; init; }
39-
public required ImmutableArray<HealthReportViewModel> HealthReports { get; init; }
40-
public KnownResourceState? KnownState { get; init; }
41+
public HealthStatus? HealthStatus { get; private set; }
42+
43+
public required ImmutableArray<HealthReportViewModel> HealthReports
44+
{
45+
get => _healthReports;
46+
init
47+
{
48+
_healthReports = value;
49+
HealthStatus = ComputeHealthStatus(value, KnownState);
50+
}
51+
}
52+
53+
public KnownResourceState? KnownState
54+
{
55+
get => _knownState;
56+
init
57+
{
58+
_knownState = value;
59+
HealthStatus = ComputeHealthStatus(_healthReports, value);
60+
}
61+
}
4162

4263
internal bool MatchesFilter(string filter)
4364
{
4465
// TODO let ResourceType define the additional data values we include in searches
4566
return Name.Contains(filter, StringComparisons.UserTextSearch);
4667
}
4768

69+
internal static HealthStatus? ComputeHealthStatus(ImmutableArray<HealthReportViewModel> healthReports, KnownResourceState? state)
70+
{
71+
if (state != KnownResourceState.Running)
72+
{
73+
return null;
74+
}
75+
76+
return healthReports.Length == 0
77+
// If there are no health reports and the resource is running, assume it's healthy.
78+
? Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Healthy
79+
// If there are health reports, the health status is the minimum of the health status of the reports.
80+
// If any of the reports is null (first health check has not returned), the health status is unhealthy.
81+
: healthReports.MinBy(r => r.HealthStatus)?.HealthStatus
82+
?? Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Unhealthy;
83+
}
84+
4885
public static string GetResourceName(ResourceViewModel resource, IDictionary<string, ResourceViewModel> allResources)
4986
{
5087
var count = 0;

src/Aspire.Dashboard/ResourceService/Partials.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ public ResourceViewModel ToViewModel(BrowserTimeProvider timeProvider, IKnownPro
4949
KnownState = HasState ? Enum.TryParse(State, out KnownResourceState knownState) ? knownState : null : null,
5050
StateStyle = HasStateStyle ? StateStyle : null,
5151
Commands = GetCommands(),
52-
HealthStatus = HasHealthStatus ? MapHealthStatus(HealthStatus) : null,
5352
HealthReports = HealthReports.Select(ToHealthReportViewModel).OrderBy(vm => vm.Name).ToImmutableArray(),
5453
};
5554
}

src/Aspire.Dashboard/Resources/Resources.Designer.cs

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Aspire.Dashboard/Resources/Resources.resx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@
233233
<value>View console logs</value>
234234
</data>
235235
<data name="ResourcesDetailsHealthStateProperty" xml:space="preserve">
236-
<value>Health State</value>
236+
<value>Health state</value>
237237
</data>
238238
<data name="ResourcesDetailsStopTimeProperty" xml:space="preserve">
239239
<value>Stop time</value>
@@ -262,4 +262,4 @@
262262
<data name="ResourceActionTelemetryTooltip" xml:space="preserve">
263263
<value>No telemetry found for this resource.</value>
264264
</data>
265-
</root>
265+
</root>

src/Aspire.Dashboard/Resources/xlf/Resources.cs.xlf

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Aspire.Dashboard/Resources/xlf/Resources.de.xlf

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Aspire.Dashboard/Resources/xlf/Resources.es.xlf

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Aspire.Dashboard/Resources/xlf/Resources.fr.xlf

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)