using System; // http://www.codeproject.com/Articles/13756/Detecting-Application-Idleness namespace SystemX.Net.Platform.Common.Util { /// /// ApplicationIdleTimer provides a convenient way of /// processing events only during application dormancy. /// Why use this instead of the Application.Idle event? /// That event gets fired EVERY TIME the message stack /// is exhausted, which basically means it fires very /// frequently. With this, you only get events when /// the application is actually idle. /// public class ApplicationIdleTimer { #region Static Members and Events // private singleton private static ApplicationIdleTimer instance = null; // Notes: // Could have utilized the System.Timers.ElapsedEventArgs, but that // only provides the time an event happend (even though it's called // "Elapsed" not "Timed" EventArgs). I figgure most listeners care // less *WHEN* the app went idle, but rather *HOW LONG* it has been // idle. /// /// EventArgs for an ApplicationIdle event. /// public class ApplicationIdleEventArgs : EventArgs { // time of last idle private DateTime _idleSince; // duration of "idleness" private TimeSpan _idleTime; /// /// Internal constructor /// /// Time app was declared idle internal ApplicationIdleEventArgs(DateTime idleSince) : base() { _idleSince = idleSince; _idleTime = new TimeSpan(DateTime.Now.Ticks - idleSince.Ticks); } /// /// Timestamp of the last time the application was "active". /// public DateTime IdleSince { get { return _idleSince; } } /// /// Duration of time the application has been idle. /// public TimeSpan IdleDuration { get { return _idleTime; } } } /// /// ApplicationIdle event handler. /// public delegate void ApplicationIdleEventHandler(ApplicationIdleEventArgs e); /// /// Hook into the ApplicationIdle event to monitor inactivity. /// It will fire AT MOST once per second. /// public static event ApplicationIdleEventHandler ApplicationIdle; #endregion #region Private Members // Timer used to guarentee perodic updates. private System.Timers.Timer _timer; // Tracks idle state private bool isIdle; // Last time application was declared "idle" private System.DateTime lastAppIdleTime; // Last time we checked for GUI activity private long lastIdleCheckpoint; // Running count of Application.Idle events recorded since a checkpoint. // Expressed as a long (instead of int) for math. private long idlesSinceCheckpoint; // Number of ticks used by application process at last checkpoint private long cpuTime; // Last time we checked for cpu activity private long lastCpuCheckpoint; // These values can be adjusted through the static properties: // Maximum "activity" (Application.Idle events per second) that will be considered "idle" // Here it is expressed as minimum ticks between idles. private long guiThreshold = TimeSpan.TicksPerMillisecond * 50L; // Maximum CPU use (percentage) that is considered "idle" private double cpuThreshold = 0.05; #endregion #region Constructors /// /// Private constructor. One instance is plenty. /// private ApplicationIdleTimer() { // Initialize counters isIdle = false; lastAppIdleTime = DateTime.Now; lastIdleCheckpoint = lastCpuCheckpoint = DateTime.UtcNow.Ticks; idlesSinceCheckpoint = cpuTime = 0; // Set up the timer and the counters _timer = new System.Timers.Timer(500); // every half-second. _timer.Enabled = true; _timer.Start(); // Hook into the events _timer.Elapsed += new System.Timers.ElapsedEventHandler(Heartbeat); System.Windows.Forms.Application.Idle += new EventHandler(Application_Idle); } /// /// Static initialization. Called once per AppDomain. /// static ApplicationIdleTimer() { // Create the singleton. if (instance == null) { instance = new ApplicationIdleTimer(); } } #endregion #region Private Methods private void Heartbeat(object sender, System.Timers.ElapsedEventArgs e) { // First we need to do here is compensate for the // "heartbeat", since it will result in an 'extra' // Idle firing .. just don't cause a divide by zero! if (idlesSinceCheckpoint > 1) idlesSinceCheckpoint--; bool newIdle = isIdle; long delta = DateTime.UtcNow.Ticks - lastIdleCheckpoint; // Determine average idle events per second. Done manually here // instead of using the ComputeGUIActivity() method to avoid the // unnecessary numeric conversion and use of a TimeSpan object. if (delta >= TimeSpan.TicksPerSecond) { // It's been over a second since last checkpoint, // so determine how "busy" the app has been over that timeframe. if (idlesSinceCheckpoint == 0 || delta / idlesSinceCheckpoint >= guiThreshold) { // Minimal gui activity. Check recent CPU activity. if (cpuThreshold < 1.0) newIdle = (ComputeCPUUsage(true) < cpuThreshold); else newIdle = true; } else { newIdle = false; } // Update counters if state changed. if (newIdle != isIdle) { isIdle = newIdle; if (newIdle) lastAppIdleTime = DateTime.Now.AddTicks(-1L * delta); } // Reset checkpoint. lastIdleCheckpoint = DateTime.UtcNow.Ticks; idlesSinceCheckpoint = 0; // Last but not least, if idle, raise the event. if (newIdle) OnApplicationIdle(); } } private void Application_Idle(object sender, EventArgs e) { // Increment idle counter. idlesSinceCheckpoint++; } internal double ComputeCPUUsage(bool resetCounters) { long delta = DateTime.UtcNow.Ticks - lastCpuCheckpoint; double pctUse = 0.0; try { // Get total time this process has used the cpu. long cpu = System.Diagnostics.Process.GetCurrentProcess().TotalProcessorTime.Ticks; // Compute usage. if (delta > 0) pctUse = ((double)(cpu - cpuTime)) / (double)delta; else pctUse = ((cpu - cpuTime) == 0 ? 0.0 : 1.0); // Update counter and checkpoint if told OR if delta is at least a quarter second. // This is to prevent inaccurate readings due to frequent OR infrequent calls. if (resetCounters || 4L * delta >= TimeSpan.TicksPerSecond) { lastCpuCheckpoint = DateTime.UtcNow.Ticks; cpuTime = cpu; // Update idle status if above threshold. if (!resetCounters && isIdle && pctUse > cpuThreshold) isIdle = false; } } catch (Exception e) { // Probably a security thing. Just ignore. pctUse = double.NaN; } return pctUse; } internal double ComputeGUIActivity() { if (idlesSinceCheckpoint <= 0) return 0.0; TimeSpan delta = new TimeSpan(DateTime.UtcNow.Ticks - lastIdleCheckpoint); if (delta.Ticks == 0) { // Clock hasn't updated yet. Return a "real" value // based on counter (either 0 or twice the threshold). return (idlesSinceCheckpoint == 0 ? 0.0 : ((double)TimeSpan.TicksPerSecond / (double)instance.guiThreshold) * 2.0); } // Expressed as activity (number of idles) per second. return ((double)idlesSinceCheckpoint / delta.TotalSeconds); // Note that this method, unlike his CPU brother, does not reset any counters. // The gui activity counters are reset once a second by the Heartbeat. } private void OnApplicationIdle() { // Check to see if anyone cares. if (ApplicationIdle == null) return; // Build the message ApplicationIdleEventArgs e = new ApplicationIdleEventArgs(this.lastAppIdleTime); // Iterate over all listeners foreach (MulticastDelegate multicast in ApplicationIdle.GetInvocationList()) { // Raise the event multicast.DynamicInvoke(new object[] { e }); } } #endregion #region Static Properties /// /// Returns the percent CPU use for the current process (0.0-1.0). /// Will return double.NaN if indeterminate. /// public static double CurrentCPUUsage { get { return instance.ComputeCPUUsage(false); } } /// /// Returns an "indication" of the gui activity, expressed as /// activity per second. 0 indicates no activity. /// GUI activity includes user interactions (typing, /// moving mouse) as well as events, paint operations, etc. /// public static double CurrentGUIActivity { get { return instance.ComputeGUIActivity(); } } /// /// Returns the *last determined* idle state. Idle state is /// recomputed once per second. Both the gui and the cpu must /// be idle for this property to be true. /// public static bool IsIdle { get { return instance.isIdle; } } /// /// The threshold (gui activity) for determining idleness. /// GUI activity below this level is considered "idle". /// public static double GUIActivityThreshold { get { return ((double)TimeSpan.TicksPerSecond / (double)instance.guiThreshold); } set { // validate value if (value <= 0.0) throw new ArgumentOutOfRangeException("GUIActivityThreshold", value, "GUIActivityThreshold must be greater than zero."); instance.guiThreshold = (long)((double)TimeSpan.TicksPerSecond / value); } } /// /// The threshold (cpu usage) for determining idleness. /// CPU usage below this level is considered "idle". /// A value >= 1.0 will disable CPU idle checking. /// public static double CPUUsageThreshold { get { return instance.cpuThreshold; } set { if (value == instance.cpuThreshold) return; // validate value if (value < 0.0) throw new ArgumentOutOfRangeException("CPUUsageThreshold", value, "Negative values are not allowed."); instance.cpuThreshold = value; } } #endregion } }