diff --git a/src/VirtualClient/VirtualClient.Contracts/VirtualClientMonitorComponent.cs b/src/VirtualClient/VirtualClient.Contracts/VirtualClientMonitorComponent.cs index 185b6739bc..56a8b60012 100644 --- a/src/VirtualClient/VirtualClient.Contracts/VirtualClientMonitorComponent.cs +++ b/src/VirtualClient/VirtualClient.Contracts/VirtualClientMonitorComponent.cs @@ -54,7 +54,9 @@ public TimeSpan? MonitorFrequency { get { - return this.Parameters.GetTimeSpanValue(nameof(this.MonitorFrequency)); + return this.Parameters.ContainsKey(nameof(this.MonitorFrequency)) + ? this.Parameters.GetTimeSpanValue(nameof(this.MonitorFrequency)) + : null; } protected set @@ -71,7 +73,9 @@ public long? MonitorIterations { get { - return this.Parameters.GetValue(nameof(this.MonitorIterations)); + return this.Parameters.ContainsKey(nameof(this.MonitorIterations)) + ? this.Parameters.GetValue(nameof(this.MonitorIterations)) + : null; } protected set @@ -104,7 +108,9 @@ public TimeSpan? MonitorWarmupPeriod { get { - return this.Parameters.GetTimeSpanValue(nameof(this.MonitorWarmupPeriod)); + return this.Parameters.ContainsKey(nameof(this.MonitorWarmupPeriod)) + ? this.Parameters.GetTimeSpanValue(nameof(this.MonitorWarmupPeriod)) + : null; } protected set @@ -163,29 +169,73 @@ protected override Task ExecuteAsync(EventContext telemetryContext, Cancellation protected abstract Task ExecuteMonitorAsync(EventContext telemetryContext, CancellationToken cancellationToken); /// - /// Executes the monitor on an interval as defined by the 'MonitorFrequency' parameter. + /// Executes the monitor on an interval as defined by the 'MonitorFrequency', 'MonitorIterations', + /// or 'MonitorStrategy' parameters. /// /// Provides information to include with telemetry. /// A token that can be used to cancel the operations. - protected Task ExecuteMonitorOnIntervalAsync(EventContext telemetryContext, CancellationToken cancellationToken) + protected async Task ExecuteMonitorOnIntervalAsync(EventContext telemetryContext, CancellationToken cancellationToken) { - while (!cancellationToken.IsCancellationRequested) + try { - try + long iterations = 0; + while (!cancellationToken.IsCancellationRequested) { + try + { + iterations++; + if (this.MonitorStrategy != null) + { + if (iterations <= 1) + { + switch (this.MonitorStrategy) + { + case VirtualClient.MonitorStrategy.Once: + case VirtualClient.MonitorStrategy.OnceAtBeginAndEnd: + await this.ExecuteMonitorAsync(telemetryContext, cancellationToken); + break; + } + } + } + else + { + if (this.IsIterationComplete(iterations)) + { + break; + } + + await this.ExecuteMonitorAsync(telemetryContext, cancellationToken); + } + } + catch (OperationCanceledException) + { + // Expected on ctrl-c or a cancellation is requested. + } + catch (Exception exc) + { + // Do not let the monitor operations crash the application. + this.Logger.LogErrorMessage(exc, telemetryContext, LogLevel.Warning); + } + finally + { + await this.WaitAsync(this.MonitorFrequency ?? TimeSpan.Zero, cancellationToken); + } } - catch (OperationCanceledException) - { - // Expected on ctrl-c or a cancellation is requested. - } - catch (Exception exc) + + if (this.MonitorStrategy == VirtualClient.MonitorStrategy.OnceAtBeginAndEnd) { - // Do not let the monitor operations crash the application. - this.Logger.LogErrorMessage(exc, telemetryContext, LogLevel.Warning); + await this.ExecuteMonitorAsync(telemetryContext, CancellationToken.None); } } - - return Task.CompletedTask; + catch (OperationCanceledException) + { + // Expected on ctrl-c or a cancellation is requested. + } + catch (Exception exc) + { + // Do not let the monitor operations crash the application. + this.Logger.LogErrorMessage(exc, telemetryContext, LogLevel.Warning); + } } /// diff --git a/src/VirtualClient/VirtualClient.Monitors/ExecuteCommandMonitor.cs b/src/VirtualClient/VirtualClient.Monitors/ExecuteCommandMonitor.cs index 4b2c3fd0d3..7312b2f84f 100644 --- a/src/VirtualClient/VirtualClient.Monitors/ExecuteCommandMonitor.cs +++ b/src/VirtualClient/VirtualClient.Monitors/ExecuteCommandMonitor.cs @@ -21,7 +21,7 @@ namespace VirtualClient.Dependencies /// package installed. /// [SupportedPlatforms("linux-arm64,linux-x64,win-arm64,win-x64")] - public class ExecuteCommandMonitor : VirtualClientIntervalBasedMonitor + public class ExecuteCommandMonitor : VirtualClientMonitorComponent { private const int MaxOutputLength = 125000; private ProcessManager processManager; @@ -138,78 +138,15 @@ public string WorkingDirectory } /// - /// Execute the monitor to run the command on intervals. + /// Executes the command(s) as the monitor operation. /// - protected override Task ExecuteAsync(EventContext telemetryContext, CancellationToken cancellationToken) + protected override Task ExecuteMonitorAsync(EventContext telemetryContext, CancellationToken cancellationToken) { - // All background monitor ExecuteAsync methods should be either 'async' or should use a Task.Run() if running a 'while' loop or the - // logic will block without returning. Monitors are typically expected to be fire-and-forget. + telemetryContext.AddContext("command", SensitiveData.ObscureSecrets(this.Command)); + telemetryContext.AddContext("workingDirectory", SensitiveData.ObscureSecrets(this.WorkingDirectory)); + telemetryContext.AddContext("platforms", string.Join(VirtualClientComponent.CommonDelimiters.First(), this.SupportedPlatforms)); - return Task.Run(async () => - { - try - { - telemetryContext.AddContext("command", SensitiveData.ObscureSecrets(this.Command)); - telemetryContext.AddContext("workingDirectory", SensitiveData.ObscureSecrets(this.WorkingDirectory)); - telemetryContext.AddContext("platforms", string.Join(VirtualClientComponent.CommonDelimiters.First(), this.SupportedPlatforms)); - - await this.WaitAsync(this.MonitorWarmupPeriod, cancellationToken); - - int iterations = 0; - while (!cancellationToken.IsCancellationRequested) - { - try - { - iterations++; - if (this.MonitorStrategy != null) - { - if (iterations <= 1) - { - switch (this.MonitorStrategy) - { - case VirtualClient.MonitorStrategy.Once: - case VirtualClient.MonitorStrategy.OnceAtBeginAndEnd: - await this.ExecuteCommandAsync(telemetryContext, cancellationToken); - break; - } - } - } - else - { - if (this.IsIterationComplete(iterations)) - { - break; - } - - await this.ExecuteCommandAsync(telemetryContext, cancellationToken); - } - } - catch (Exception exc) - { - // Do not let the monitor operations crash the application. - this.Logger.LogErrorMessage(exc, telemetryContext, LogLevel.Warning); - } - finally - { - await this.WaitAsync(this.MonitorFrequency, cancellationToken); - } - } - - if (this.MonitorStrategy == VirtualClient.MonitorStrategy.OnceAtBeginAndEnd) - { - await this.ExecuteCommandAsync(telemetryContext, CancellationToken.None); - } - } - catch (OperationCanceledException) - { - // Expected on ctrl-c or a cancellation is requested. - } - catch (Exception exc) - { - // Do not let the monitor operations crash the application. - this.Logger.LogErrorMessage(exc, telemetryContext, LogLevel.Warning); - } - }); + return this.ExecuteCommandAsync(telemetryContext, cancellationToken); } ///