diff --git a/ATxCommon/ATxCommon.csproj b/ATxCommon/ATxCommon.csproj index 96b7ac83d3d63bb003ea2210b47566ca1f5342fd..d281301c0b8684077a38c1f8a74b267414ed8809 100644 --- a/ATxCommon/ATxCommon.csproj +++ b/ATxCommon/ATxCommon.csproj @@ -47,6 +47,7 @@ <Compile Include="BuildDetails.cs" /> <Compile Include="Conv.cs" /> <Compile Include="FsUtils.cs" /> + <Compile Include="Monitoring\Cpu.cs" /> <Compile Include="NLog\RateLimitWrapper.cs" /> <Compile Include="Serializables\DriveToCheck.cs" /> <Compile Include="Serializables\ServiceConfig.cs" /> diff --git a/ATxCommon/Monitoring/Cpu.cs b/ATxCommon/Monitoring/Cpu.cs new file mode 100644 index 0000000000000000000000000000000000000000..71aeb8529149404e0a0f3be008883fac789996a9 --- /dev/null +++ b/ATxCommon/Monitoring/Cpu.cs @@ -0,0 +1,199 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Timers; +using NLog; +using Timer = System.Timers.Timer; + +namespace ATxCommon.Monitoring +{ + /// <summary> + /// CPU load monitoring class, constantly checking the current load at the given <see + /// cref="Interval"/> using a separate timer (thus running in its own thread). + /// + /// The load is determined using a <see cref="PerformanceCounter"/>, and is compared against + /// a configurable <see cref="Limit"/>. If the load changes from below the limit to above, a + /// <see cref="LoadAboveLimit"/> event will be raised. If the load has been above the limit + /// and is then dropping below, an <see cref="OnLoadBelowLimit"/> event will be raised as soon + /// as a given number of consecutive measurements (defined via <see cref="Probation"/>) were + /// found to be below the limit. + /// </summary> + public class Cpu + { + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + + /// <summary> + /// The generic event handler delegate for CPU events. + /// </summary> + public delegate void EventHandler(object sender, EventArgs e); + + /// <summary> + /// Event raised when the CPU load exceeds the configured limit for any measurement. + /// </summary> + public event EventHandler LoadAboveLimit; + + /// <summary> + /// Event raised when the CPU load is below the configured limit for at least four + /// consecutive measurements after having exceeded this limit before. + /// </summary> + public event EventHandler LoadBelowLimit; + + private readonly Timer _monitoringTimer; + private readonly PerformanceCounter _cpuCounter; + private readonly float[] _loadReadings = {0F, 0F, 0F, 0F}; + + private int _interval; + private int _limit; + private int _behaving; + private int _probation; + + + /// <summary> + /// Current CPU load (usage percentage over all cores), averaged of the last four readings. + /// </summary> + /// <returns>The average CPU load from the last four readings.</returns> + public float Load { get; private set; } + + /// <summary> + /// Flag representing whether the load is considered to be high or low. + /// </summary> + public bool HighLoad { get; private set; } + + /// <summary> + /// How often (in ms) to check the CPU load. + /// </summary> + public int Interval { + get => _interval; + set { + _interval = value; + _monitoringTimer.Interval = value; + Log.Debug("CPU monitoring interval: {0}", _interval); + } + } + + /// <summary> + /// Upper limit of CPU load (usage in % over all cores) before it is classified as "high". + /// </summary> + public int Limit { + get => _limit; + set { + _limit = value; + Log.Debug("CPU monitoring limit: {0}", _limit); + } + } + + /// <summary> + /// Number of cycles where the CPU load value has to be below the limit before it is + /// classified as "low" again. + /// </summary> + public int Probation { + get => _probation; + set { + _probation = value; + Log.Debug("CPU monitoring probation cycles when violating limit: {0}", _probation); + } + } + + /// <summary> + /// Indicating whether the CPU load monitoring is active. + /// </summary> + public bool Enabled { + get => _monitoringTimer.Enabled; + set { + Log.Debug("{0} CPU monitoring.", value ? "Enabling" : "Disabling"); + _monitoringTimer.Enabled = value; + } + } + + + + /// <summary> + /// Create performance counter and initialize it. + /// </summary> + public Cpu() { + _interval = 250; + _limit = 25; + _probation = 40; + Log.Debug("Initializing CPU monitoring..."); + try { + _cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total"); + Log.Debug("CPU monitoring initializing PerformanceCounter (takes one second)..."); + _cpuCounter.NextValue(); + Thread.Sleep(1000); + var curLoad = _cpuCounter.NextValue(); + Log.Debug("CPU monitoring current load: {0:0.0}", curLoad); + // now initialize the load state: + HighLoad = curLoad > _limit; + _monitoringTimer = new Timer(_interval); + _monitoringTimer.Elapsed += UpdateCpuLoad; + } + catch (Exception) { + Log.Error("Initializing CPU monitoring failed!"); + throw; + } + + Log.Debug("Initializing CPU monitoring completed."); + } + + /// <summary> + /// Check current CPU load, update the history of readings and trigger the corresponding + /// events if the required criteria are met. + /// </summary> + private void UpdateCpuLoad(object sender, ElapsedEventArgs e) { + _monitoringTimer.Enabled = false; + try { + // ConstrainedCopy seems to be the most efficient approach to shift the array: + Array.ConstrainedCopy(_loadReadings, 1, _loadReadings, 0, 3); + _loadReadings[3] = _cpuCounter.NextValue(); + Load = _loadReadings.Average(); + if (_loadReadings[3] > _limit) { + if (_behaving > _probation) { + // this means the load was considered as "low" before, so raise an event: + OnLoadAboveLimit(); + Log.Trace("CPU load ({0:0.0}) violating limit ({1})!", _loadReadings[3], _limit); + } else if (_behaving > 0) { + // this means we were still in probation, so no need to trigger again... + Log.Trace("Resetting behaving counter to 0 (was {0}).", _behaving); + } + _behaving = 0; + } else { + _behaving++; + if (_behaving == _probation) { + Log.Trace("CPU load below limit for {0} cycles, passing probation!", _probation); + OnLoadBelowLimit(); + } else if (_behaving > _probation) { + Log.Trace("CPU load behaving well since {0} cycles.", _behaving); + } else if (_behaving < 0) { + Log.Warn("Integer wrap around happened, resetting probation counter!"); + _behaving = _probation + 1; + } + } + } + catch (Exception ex) { + Log.Error("UpdateCpuLoad failed: {0}", ex.Message); + } + finally { + _monitoringTimer.Enabled = true; + } + Log.Trace("CPU load: {0:0.0} {1}", _loadReadings[3], + _loadReadings[3] < Limit ? " [" + _behaving + "]" : ""); + } + + /// <summary> + /// Raise the "LoadAboveLimit" event. + /// </summary> + protected virtual void OnLoadAboveLimit() { + HighLoad = true; + LoadAboveLimit?.Invoke(this, EventArgs.Empty); + } + + /// <summary> + /// Raise the "LoadBelowLimit" event. + /// </summary> + protected virtual void OnLoadBelowLimit() { + HighLoad = false; + LoadBelowLimit?.Invoke(this, EventArgs.Empty); + } + } +} diff --git a/ATxCommon/Serializables/ServiceStatus.cs b/ATxCommon/Serializables/ServiceStatus.cs index a7082c0aa9345ac4adc92c254b162478b43767c2..5df0e42f97ef1261d58aa3a6c1f953cb8eff457b 100644 --- a/ATxCommon/Serializables/ServiceStatus.cs +++ b/ATxCommon/Serializables/ServiceStatus.cs @@ -20,7 +20,7 @@ namespace ATxCommon.Serializables private DateTime _lastAdminNotification; private DateTime _lastGraceNotification; - private string _limitReason; + private string _statusDescription; private string _currentTransferSrc; private string _currentTargetTmp; @@ -151,13 +151,13 @@ namespace ATxCommon.Serializables /// <summary> /// String indicating why the service is currently suspended (empty if not suspended). /// </summary> - public string LimitReason { - get => _limitReason; + public string StatusDescription { + get => _statusDescription; set { - if (_limitReason == value) return; + if (_statusDescription == value) return; - _limitReason = value; - Log.Trace("LimitReason was updated ({0}).", value); + _statusDescription = value; + Log.Trace("StatusDescription was updated ({0}).", value); Serialize(); } } @@ -286,6 +286,27 @@ namespace ATxCommon.Serializables Environment.MachineName, _currentTargetTmp); } + + /// <summary> + /// Helper to set the service state, logging a message if the state has changed. + /// </summary> + /// <param name="suspended">The target state for <see cref="ServiceSuspended"/>.</param> + /// <param name="description">The description to use for the log message as well as for + /// setting <see cref="StatusDescription"/>.</param> + public void SetSuspended(bool suspended, string description) { + if (description == _statusDescription && suspended == _serviceSuspended) + return; + + _serviceSuspended = suspended; + _statusDescription = description; + Serialize(); + + if (suspended) { + Log.Info("Service suspended. Reason(s): [{0}]", description); + } else { + Log.Info("Service resuming operation ({0}).", description); + } + } #region validate and report diff --git a/ATxCommon/SystemChecks.cs b/ATxCommon/SystemChecks.cs index 0fc8b2fe9e955481f41ff210160a10a895ed7aad..eb412ce481ba100a8f6d49e1beec6364ec25a475 100644 --- a/ATxCommon/SystemChecks.cs +++ b/ATxCommon/SystemChecks.cs @@ -15,7 +15,7 @@ namespace ATxCommon /// <summary> /// Get the available physical memory in MB. /// </summary> - /// <returns>The available physical memory in MB or -1 in case of an error.</returns> + /// <returns>Available physical memory in MB or -1 in case of an error.</returns> public static long GetFreeMemory() { try { var searcher = @@ -26,30 +26,7 @@ namespace ATxCommon } } catch (Exception ex) { - Log.Warn("Error in GetFreeMemory: {0}", ex.Message); - } - - return -1; - } - - /// <summary> - /// Get the CPU usage in percent over all cores. - /// </summary> - /// <returns>CPU usage in percent or -1 if an error occured.</returns> - public static int GetCpuUsage() { - // TODO: fix bug #36 - try { - var searcher = new ManagementObjectSearcher("select * from Win32_PerfFormattedData_PerfOS_Processor"); - foreach (var mo in searcher.Get()) { - var obj = (ManagementObject)mo; - var usage = obj["PercentProcessorTime"]; - var name = obj["Name"]; - if (name.ToString().Equals("_Total")) - return Convert.ToInt32(usage); - } - } - catch (Exception ex) { - Log.Warn("Error in GetCpuUsage: {0}", ex.Message); + Log.Trace("Error in GetFreeMemory: {0}", ex.Message); } return -1; diff --git a/ATxDiagnostics/ATxDiagnostics.cs b/ATxDiagnostics/ATxDiagnostics.cs new file mode 100644 index 0000000000000000000000000000000000000000..f3b80fb81e11a53514f5fe5ee46bd04fcc0914d3 --- /dev/null +++ b/ATxDiagnostics/ATxDiagnostics.cs @@ -0,0 +1,96 @@ +using System; +using System.Diagnostics; +using System.Management; +using System.Reflection; +using ATxCommon; +using ATxCommon.Monitoring; +using NLog; +using NLog.Config; +using NLog.Targets; + +namespace ATxDiagnostics +{ + class ATxDiagnostics + { + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + + static void Main(string[] args) { + var loglevel = LogLevel.Debug; + if (args.Length > 0 && args[0] == "trace") { + loglevel = LogLevel.Trace; + } + var logConfig = new LoggingConfiguration(); + var logTargetConsole = new ConsoleTarget { + Name = "console", + Header = "AutoTx Diagnostics", + Layout = @"${time} [${level}] ${message}", + }; + logConfig.AddTarget(logTargetConsole); + var logRuleConsole = new LoggingRule("*", loglevel, logTargetConsole); + logConfig.LoggingRules.Add(logRuleConsole); + LogManager.Configuration = logConfig; + + var commonAssembly = Assembly.GetAssembly(typeof(ATxCommon.Monitoring.Cpu)); + var commonVersionInfo = FileVersionInfo.GetVersionInfo(commonAssembly.Location); + Log.Info("ATxCommon library version: {0}", commonVersionInfo.ProductVersion); + + Log.Debug("Free space on drive [C:]: " + Conv.BytesToString(SystemChecks.GetFreeDriveSpace("C:"))); + + Log.Info("Checking CPU load using ATxCommon.Monitoring..."); + var cpu = new Cpu { + Interval = 250, + Limit = 25, + Probation = 4, // 4 * 250 ms = 1 second + Enabled = true + }; + System.Threading.Thread.Sleep(10000); + cpu.Enabled = false; + Log.Info("Finished checking CPU load using ATxCommon.Monitoring.\n"); + + Log.Info("Checking CPU load using WMI..."); + for (int i = 0; i < 10; i++) { + WmiQueryCpuLoad(); + System.Threading.Thread.Sleep(1000); + } + Log.Info("Finished checking CPU load using WMI.\n"); + } + + private static int WmiQueryCpuLoad() { + Log.Trace("Querying WMI for CPU load..."); + var watch = Stopwatch.StartNew(); + var queryString = "SELECT Name, PercentProcessorTime " + + "FROM Win32_PerfFormattedData_PerfOS_Processor"; + var opts = new EnumerationOptions { + Timeout = new TimeSpan(0, 0, 10), + ReturnImmediately = false, + }; + var searcher = new ManagementObjectSearcher("", queryString, opts); + Int32 usageInt32 = -1; + + var managementObjects = searcher.Get(); + if (managementObjects.Count > 0) { + Log.Trace("WMI query returned {0} objects.", managementObjects.Count); + foreach (var mo in managementObjects) { + var obj = (ManagementObject)mo; + var usage = obj["PercentProcessorTime"]; + var name = obj["Name"]; + + usageInt32 = Convert.ToInt32(usage); + Log.Debug("CPU usage {1}: {0}", usageInt32, name); + + } + } else { + Log.Error("No objects returned from WMI!"); + } + + managementObjects.Dispose(); + searcher.Dispose(); + + watch.Stop(); + Log.Trace("WMI query took {0} ms.", watch.ElapsedMilliseconds); + + return usageInt32; + } + + } +} diff --git a/ATxDiagnostics/ATxDiagnostics.csproj b/ATxDiagnostics/ATxDiagnostics.csproj new file mode 100644 index 0000000000000000000000000000000000000000..140cd0fd36f439cc19e9b0d5d4a2c510323d2893 --- /dev/null +++ b/ATxDiagnostics/ATxDiagnostics.csproj @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{1FDA9634-87C9-4C25-AD12-BF79DA61D44D}</ProjectGuid> + <OutputType>Exe</OutputType> + <RootNamespace>ATxDiagnostics</RootNamespace> + <AssemblyName>AutoTxDiagnostics</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> + <TargetFrameworkProfile /> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants> + </DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> + <HintPath>..\packages\NLog.4.3.11\lib\net45\NLog.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Core" /> + </ItemGroup> + <ItemGroup> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Management" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="ATxDiagnostics.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="App.config" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\ATxCommon\ATxCommon.csproj"> + <Project>{166D65D5-EE10-4364-8AA3-4D86BA5CE244}</Project> + <Name>ATxCommon</Name> + </ProjectReference> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <PropertyGroup> + <PreBuildEvent>PowerShell -NoProfile -ExecutionPolicy RemoteSigned $(SolutionDir)Scripts\Prepare-Build.ps1 -SolutionDir $(SolutionDir) -ConfigurationName $(ConfigurationName)</PreBuildEvent> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/ATxDiagnostics/App.config b/ATxDiagnostics/App.config new file mode 100644 index 0000000000000000000000000000000000000000..d1428ad712d72a50520dab5091ddcb5ea2b5f3e6 --- /dev/null +++ b/ATxDiagnostics/App.config @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<configuration> + <startup> + <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> + </startup> +</configuration> diff --git a/ATxDiagnostics/Properties/AssemblyInfo.cs b/ATxDiagnostics/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..31539f5897346cc8b639a8421e160b9ef839da4c --- /dev/null +++ b/ATxDiagnostics/Properties/AssemblyInfo.cs @@ -0,0 +1,44 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using ATxCommon; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoTx Diagnostics")] +[assembly: AssemblyDescription("AutoTx Command Line Diagnostics Tool")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("IMCF, Biozentrum, University of Basel")] +[assembly: AssemblyProduct("AutoTx")] +[assembly: AssemblyCopyright("© University of Basel 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("1fda9634-87c9-4c25-ad12-bf79da61d44d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion(BuildDetails.GitMajor + "." + + BuildDetails.GitMinor + "." + + BuildDetails.GitPatch + ".0")] +[assembly: AssemblyFileVersion(BuildDetails.GitMajor + "." + + BuildDetails.GitMinor + "." + + BuildDetails.GitPatch + ".0")] + +[assembly: AssemblyInformationalVersion(BuildDetails.BuildDate + + " " + BuildDetails.GitCommit + + " (" + BuildDetails.GitBranch + ")")] \ No newline at end of file diff --git a/ATxService/AutoTx.cs b/ATxService/AutoTx.cs index 18cb06aff7ed2d5f94d9a05a6205cd98c0fb594d..58cbcc9833e25effec1415d5ecc1cf27ff4018dd 100644 --- a/ATxService/AutoTx.cs +++ b/ATxService/AutoTx.cs @@ -8,6 +8,7 @@ using System.IO; using System.Reflection; using System.Timers; using ATxCommon; +using ATxCommon.Monitoring; using ATxCommon.NLog; using ATxCommon.Serializables; using NLog; @@ -39,9 +40,26 @@ namespace ATxService /// </summary> private readonly List<string> _incomingIgnore = new List<string>(); + /// <summary> + /// The CPU load monitoring object. + /// </summary> + private readonly Cpu _cpu; + private RoboCommand _roboCommand; + + /// <summary> + /// Size of the file currently being transferred (in bytes). Zero if no transfer running. + /// </summary> private long _txCurFileSize; + + /// <summary> + /// Progress (in percent) of the file currently being transferred. Zero if no transfer. + /// </summary> private int _txCurFileProgress; + + /// <summary> + /// Internal counter to introduce a delay between two subsequent transfers. + /// </summary> private int _waitCyclesBeforeNextTx; private DateTime _lastUserDirCheck = DateTime.MinValue; @@ -89,6 +107,28 @@ namespace ATxService LoadSettings(); CreateIncomingDirectories(); + try { + _cpu = new Cpu { + Interval = 250, + Limit = _config.MaxCpuUsage, + Probation = 16, + Enabled = true + }; + _cpu.LoadAboveLimit += OnLoadAboveLimit; + _cpu.LoadBelowLimit += OnLoadBelowLimit; + } + catch (UnauthorizedAccessException ex) { + Log.Error("Not enough permissions to monitor the CPU load.\nMake sure the " + + "service account is a member of the [Performance Monitor Users] " + + "system group.\nError message was: {0}", ex.Message); + throw; + } + catch (Exception ex) { + Log.Error("Unexpected error initializing CPU monitoring: {0}", ex.Message); + throw; + } + + if (_config.DebugRoboSharp) { Debugger.Instance.DebugMessageEvent += HandleDebugMessage; Log.Debug("Enabled RoboSharp debug logging."); @@ -496,52 +536,56 @@ namespace ATxService #region general methods + /// <summary> + /// Event handler for CPU load dropping below the configured limit. + /// </summary> + private void OnLoadBelowLimit(object sender, EventArgs e) { + Log.Trace("Received a low-CPU-load event!"); + ResumePausedTransfer(); + } + + /// <summary> + /// Event handler for CPU load exceeding the configured limit. + /// </summary> + private void OnLoadAboveLimit(object sender, EventArgs e) { + Log.Trace("Received a high-CPU-load event!"); + PauseTransfer(); + } + /// <summary> /// Check system parameters for valid ranges and update the global service state accordingly. /// </summary> private void UpdateServiceState() { - var limitReason = ""; + var suspendReasons = new List<string>(); // check all system parameters for valid ranges and remember the reason in a string // if one of them is failing (to report in the log why we're suspended) - if (SystemChecks.GetCpuUsage() >= _config.MaxCpuUsage) - limitReason = "CPU usage"; - else if (SystemChecks.GetFreeMemory() < _config.MinAvailableMemory) - limitReason = "RAM usage"; - else { - var blacklistedProcess = SystemChecks.CheckForBlacklistedProcesses( - _config.BlacklistedProcesses); - if (blacklistedProcess != "") { - limitReason = "blacklisted process '" + blacklistedProcess + "'"; - } + if (_cpu.HighLoad) + suspendReasons.Add("CPU"); + + if (SystemChecks.GetFreeMemory() < _config.MinAvailableMemory) + suspendReasons.Add("RAM"); + + var blacklistedProcess = SystemChecks.CheckForBlacklistedProcesses( + _config.BlacklistedProcesses); + if (!string.IsNullOrWhiteSpace(blacklistedProcess)) { + suspendReasons.Add("process '" + blacklistedProcess + "'"); } // all parameters within valid ranges, so set the state to "Running": - if (string.IsNullOrEmpty(limitReason)) { - _status.ServiceSuspended = false; - if (!string.IsNullOrEmpty(_status.LimitReason)) { - _status.LimitReason = ""; // reset to force a message on next service suspend - Log.Info("Service resuming operation (all parameters in valid ranges)."); - } + if (suspendReasons.Count == 0) { + _status.SetSuspended(false, "all parameters in valid ranges"); return; } // set state to "Running" if no-one is logged on: if (SystemChecks.NoUserIsLoggedOn()) { - _status.ServiceSuspended = false; - if (!string.IsNullOrEmpty(_status.LimitReason)) { - _status.LimitReason = ""; // reset to force a message on next service suspend - Log.Info("Service resuming operation (no user logged on)."); - } + _status.SetSuspended(false, "no user is currently logged on"); return; } // by reaching this point we know the service should be suspended: - _status.ServiceSuspended = true; - if (limitReason == _status.LimitReason) - return; - Log.Info("Service suspended due to limitiations [{0}].", limitReason); - _status.LimitReason = limitReason; + _status.SetSuspended(true, string.Join(", ", suspendReasons)); } /// <summary> diff --git a/ATxTray/AutoTxTray.cs b/ATxTray/AutoTxTray.cs index fe0b01fa528d7fc9172711f1c9440ef51cfe1e82..eecbc91b0b9f3e678574723f9b795dcf4cb412b9 100644 --- a/ATxTray/AutoTxTray.cs +++ b/ATxTray/AutoTxTray.cs @@ -459,18 +459,18 @@ namespace ATxTray private void UpdateServiceSuspendedState() { // first update the suspend reason as this can possibly change even if the service // never leaves the suspended state and we should still display the correct reason: - if (_serviceSuspendReason == _status.LimitReason && + if (_serviceSuspendReason == _status.StatusDescription && _serviceSuspended == _status.ServiceSuspended) return; _serviceSuspended = _status.ServiceSuspended; - _serviceSuspendReason = _status.LimitReason; + _serviceSuspendReason = _status.StatusDescription; if (_serviceSuspended) { _miSvcSuspended.Text = @"Service suspended, reason: " + _serviceSuspendReason; _miSvcSuspended.BackColor = Color.LightSalmon; /* _notifyIcon.ShowBalloonTip(500, "AutoTx Monitor", - "Service suspended: " + _status.LimitReason, ToolTipIcon.Warning); + "Service suspended: " + _status.StatusDescription, ToolTipIcon.Warning); */ } else { _miSvcSuspended.Text = @"No limits apply, service active."; diff --git a/AutoTx.sln b/AutoTx.sln index 920d1a1d393e6bb2507498f19a8469711a7b4e01..81a8bca68b5a45e515b1ec39b558baced6cbcf2d 100644 --- a/AutoTx.sln +++ b/AutoTx.sln @@ -24,6 +24,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Resources\Mail-Templates\Transfer-Success.txt = Resources\Mail-Templates\Transfer-Success.txt EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ATxDiagnostics", "ATxDiagnostics\ATxDiagnostics.csproj", "{1FDA9634-87C9-4C25-AD12-BF79DA61D44D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -50,6 +52,10 @@ Global {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.Build.0 = Debug|Any CPU {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.ActiveCfg = Release|Any CPU {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.Build.0 = Release|Any CPU + {1FDA9634-87C9-4C25-AD12-BF79DA61D44D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FDA9634-87C9-4C25-AD12-BF79DA61D44D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FDA9634-87C9-4C25-AD12-BF79DA61D44D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FDA9634-87C9-4C25-AD12-BF79DA61D44D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE