Search code examples
.net-core.net-core-3.0

HealthChecksUI + memory (GCInfo)


How would you go about displaying Garbage Collection (GC) info in HealthChecksUI in .NET Core 3?

NuGet ref: https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks

I've been looking at samples here and there, but can't really find what I'm looking for. What I'm trying to do is do present the GC memory allocation to HC-UI and report degraded if it surpasses some limit. And I got it working - but I believe the implementation can be a lot better due to heap allocation when checking it.

Concerned section marked with comment @ startup.cs

Here's my example:

startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.
        .AddCustomHealthChecks()
        .AddHealthChecksUI();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app
        .UseRouting()
        .UseEndpoints(endpoints =>
            {
                endpoints.MapHealthChecks("/health", new HealthCheckOptions
                {
                    Predicate = (check) =>
                        check.Tags.Contains("self")
                        || check.Tags.Contains("memory"),
                    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
                });
                endpoints.MapHealthChecks("/liveness", new HealthCheckOptions
                {
                    Predicate = (check) => check.Tags.Contains("self")
                });
                endpoints.MapHealthChecksUI(setup =>
                {
                    setup.AddCustomStylesheet("healthcheck-ui.css");
                });
            });
}

public static IServiceCollection AddCustomHealthChecks(this IServiceCollection services)
{
        services
            .AddHealthChecks()
            .AddCheck(
                "Self", () =>
                HealthCheckResult.Healthy("Dynamic Config is OK!"),
                tags: new[] { "self" }
            )
            .AddCheck("Memory", () =>                
                new GCInfoHealthCheck()            // This section right here
                    .CheckHealthAsync(             // seems to be a very
                        new HealthCheckContext()   // poor implementation due
                    ).GetAwaiter().GetResult(),    // to constant Heap allocation.
                tags: new[] { "memory" }
            );

        return services;
}

GCInfoHealthCheck.cs

public class GCInfoHealthCheck : IHealthCheck
{
    public string Name { get; } = "GCInfo";

    public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken))
    {            
        var allocatedMegaBytes = GC.GetTotalMemory(forceFullCollection: false) / 1000000; // divided to get MB
        var data = new Dictionary<string, object>()
        {
            { "AllocatedMegaBytes", allocatedMegaBytes }
        };

        var status = (allocatedMegaBytes >= 20) ? HealthStatus.Unhealthy : HealthStatus.Healthy;

        return Task.FromResult(
            new HealthCheckResult(
                status,
                exception: null,
                description: $"reports degraded status if allocated MB >= 20MB, current: {allocatedMegaBytes} MB",
                data: data
            )
        );
    }        
}

Output

HC Ok HC not Ok

Here are some samples I've been looking at: https://github.com/aspnet/Diagnostics/tree/d1cba1f55bab1e3b206a46cee81eb1583d8732e2/samples/HealthChecksSample

I've been trying some other samples and register HC's as singletons, but I can't get it to work and report to HC-UI. So, is there better way to do it?


Solution

  • Seems like I've been a potato and probably misspelled the tag, because this works like a charm:

    // register
    .AddCheck<GCInfoHealthCheck>(
        "memory",
        failureStatus: HealthStatus.Unhealthy,
        tags: new[] { "memory" }
    )
    
    
    // useage
    .UseHealthChecks("/health", new HealthCheckOptions()
    {
          Predicate = (check) =>
             || check.Tags.Contains("memory"),
             ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
    })
    

    GC HC

    Docs: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-3.1