diff --git a/src/Microsoft.Extensions.Diagnostics.HealthChecks/DefaultHealthCheckService.cs b/src/Microsoft.Extensions.Diagnostics.HealthChecks/DefaultHealthCheckService.cs index f1a41ea303..1b08acb60b 100644 --- a/src/Microsoft.Extensions.Diagnostics.HealthChecks/DefaultHealthCheckService.cs +++ b/src/Microsoft.Extensions.Diagnostics.HealthChecks/DefaultHealthCheckService.cs @@ -73,30 +73,40 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks try { var result = await healthCheck.CheckHealthAsync(context, cancellationToken); + var duration = stopwatch.GetElapsedTime(); entry = new HealthReportEntry( - result.Result ? HealthStatus.Healthy : registration.FailureStatus, - result.Description, - result.Exception, - result.Data); + status: result.Result ? HealthStatus.Healthy : registration.FailureStatus, + description: result.Description, + duration: duration, + exception: result.Exception, + data: result.Data); - Log.HealthCheckEnd(_logger, registration, entry, stopwatch.GetElapsedTime()); + Log.HealthCheckEnd(_logger, registration, entry, duration); Log.HealthCheckData(_logger, registration, entry); } // Allow cancellation to propagate. catch (Exception ex) when (ex as OperationCanceledException == null) { - entry = new HealthReportEntry(HealthStatus.Failed, ex.Message, ex, data: null); - Log.HealthCheckError(_logger, registration, ex, stopwatch.GetElapsedTime()); + var duration = stopwatch.GetElapsedTime(); + entry = new HealthReportEntry( + status: HealthStatus.Failed, + description: ex.Message, + duration: duration, + exception: ex, + data: null); + + Log.HealthCheckError(_logger, registration, ex, duration); } entries[registration.Name] = entry; } } - var report = new HealthReport(entries); - Log.HealthCheckProcessingEnd(_logger, report.Status, totalTime.GetElapsedTime()); + var totalElapsedTime = totalTime.GetElapsedTime(); + var report = new HealthReport(entries, totalElapsedTime); + Log.HealthCheckProcessingEnd(_logger, report.Status, totalElapsedTime); return report; } } diff --git a/src/Microsoft.Extensions.Diagnostics.HealthChecks/HealthReport.cs b/src/Microsoft.Extensions.Diagnostics.HealthChecks/HealthReport.cs index 29359a547f..a0e6fac1cd 100644 --- a/src/Microsoft.Extensions.Diagnostics.HealthChecks/HealthReport.cs +++ b/src/Microsoft.Extensions.Diagnostics.HealthChecks/HealthReport.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; namespace Microsoft.Extensions.Diagnostics.HealthChecks @@ -14,10 +15,12 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks /// Create a new from the specified results. /// /// A containing the results from each health check. - public HealthReport(IReadOnlyDictionary entries) + /// A value indicating the time the health check service took to execute. + public HealthReport(IReadOnlyDictionary entries, TimeSpan totalDuration) { Entries = entries; Status = CalculateAggregateStatus(entries.Values); + TotalDuration = totalDuration; } /// @@ -35,6 +38,11 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks /// public HealthStatus Status { get; } + /// + /// Gets the time the health check service took to execute. + /// + public TimeSpan TotalDuration { get; } + private HealthStatus CalculateAggregateStatus(IEnumerable entries) { // This is basically a Min() check, but we know the possible range, so we don't need to walk the whole list diff --git a/src/Microsoft.Extensions.Diagnostics.HealthChecks/HealthReportEntry.cs b/src/Microsoft.Extensions.Diagnostics.HealthChecks/HealthReportEntry.cs index 17ed5ae288..2898c93aa4 100644 --- a/src/Microsoft.Extensions.Diagnostics.HealthChecks/HealthReportEntry.cs +++ b/src/Microsoft.Extensions.Diagnostics.HealthChecks/HealthReportEntry.cs @@ -19,12 +19,14 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks /// /// A value indicating the health status of the component that was checked. /// A human-readable description of the status of the component that was checked. + /// A value indicating the health execution duration. /// An representing the exception that was thrown when checking for status (if any). /// Additional key-value pairs describing the health of the component. - public HealthReportEntry(HealthStatus status, string description, Exception exception, IReadOnlyDictionary data) + public HealthReportEntry(HealthStatus status, string description, TimeSpan duration, Exception exception, IReadOnlyDictionary data) { Status = status; Description = description; + Duration = duration; Exception = exception; Data = data ?? _emptyReadOnlyDictionary; } @@ -39,6 +41,11 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks /// public string Description { get; } + /// + /// Gets the health check execution duration. + /// + public TimeSpan Duration { get; } + /// /// Gets an representing the exception that was thrown when checking for status (if any). /// diff --git a/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests/HealthReportTest.cs b/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests/HealthReportTest.cs index 9ce0a4b620..453398e639 100644 --- a/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests/HealthReportTest.cs +++ b/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests/HealthReportTest.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using Xunit; @@ -17,15 +18,29 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks { var result = new HealthReport(new Dictionary() { - {"Foo", new HealthReportEntry(HealthStatus.Healthy, null, null, null) }, - {"Bar", new HealthReportEntry(HealthStatus.Healthy, null, null, null) }, - {"Baz", new HealthReportEntry(status, exception: null, description: null, data: null) }, - {"Quick", new HealthReportEntry(HealthStatus.Healthy, null, null, null) }, - {"Quack", new HealthReportEntry(HealthStatus.Healthy, null, null, null) }, - {"Quock", new HealthReportEntry(HealthStatus.Healthy, null, null, null) }, - }); + {"Foo", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null) }, + {"Bar", new HealthReportEntry(HealthStatus.Healthy, null, TimeSpan.MinValue,null, null) }, + {"Baz", new HealthReportEntry(status, exception: null, description: null,duration:TimeSpan.MinValue, data: null) }, + {"Quick", new HealthReportEntry(HealthStatus.Healthy, null, TimeSpan.MinValue, null, null) }, + {"Quack", new HealthReportEntry(HealthStatus.Healthy, null, TimeSpan.MinValue, null, null) }, + {"Quock", new HealthReportEntry(HealthStatus.Healthy, null, TimeSpan.MinValue, null, null) }, + }, totalDuration: TimeSpan.MinValue); Assert.Equal(status, result.Status); } + + [Theory] + [InlineData(200)] + [InlineData(300)] + [InlineData(400)] + public void TotalDuration_MatchesTotalDurationParameter(int milliseconds) + { + var result = new HealthReport(new Dictionary() + { + {"Foo", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null) } + }, totalDuration: TimeSpan.FromMilliseconds(milliseconds)); + + Assert.Equal(TimeSpan.FromMilliseconds(milliseconds), result.TotalDuration); + } } }