Use the Sytem.Diagnostics.PerformanceCounter class and attach to the PhysicalDisk counter for the drive that you are indexing.
Below is some code from disklight.codeplex.com, although its currently hard-wired to the "C:" drive. You will want to change "C:" to whichever drive your process is scanning.
The counter's I would watch would probably be "% Idle Time", which pretty much says how often the drive is doing anything. 0% idle just means the disk is busy, but does not necessarily mean that it is flat-out and cannot transfer more data.
Combine the % Idle Time with "Current Disk Queue Length" and this will tell you if the drive is getting so busy that it cannot service all the requests for data. As guidelines, anything over 0 means drive is probably flat-out busy, and anything over 2 means the drive is completely saturated. These would probably apply to both SSD and HDD fairly well.
Also, any value that you read is an instantaneous value at a point in time. You should do a running average over a few results, e.g. take a reading every 100ms, and average 5 readings before using the information from the result to make a decision... like waiting until the counters settle before making your next IO request.
public struct DiskUsageStats
{
public int DiskQueueLength;
public int DiskUsagePercent;
public string DriveName;
public int ReadBytesPerSec;
public int WriteBytesPerSec;
}
internal class DiskUsageMonitor
{
private readonly PerformanceCounter _diskQueueCounter;
private readonly PerformanceCounter _idleCounter;
private Timer _perfTimer;
private readonly PerformanceCounter _readBytesCounter;
private int _updateResolutionMillisecs = 100;
private readonly PerformanceCounter _writeBytesCounter;
/// <summary>
/// Initializes a new instance of the <see cref="DiskUsageMonitor"/> class.
/// </summary>
/// <param name="updateResolutionMillisecs">The update resolution millisecs.</param>
internal DiskUsageMonitor(int updateResolutionMillisecs)
: this(null)
{
_updateResolutionMillisecs = updateResolutionMillisecs;
}
/// <summary>
/// Initializes a new instance of the <see cref="DiskUsageMonitor"/> class.
/// </summary>
/// <param name="updateResolutionMillisecs">The update resolution millisecs.</param>
/// <param name="driveName">Name of the drive.</param>
internal DiskUsageMonitor(int updateResolutionMillisecs, string driveName)
: this(driveName)
{
_updateResolutionMillisecs = updateResolutionMillisecs;
}
/// <summary>
/// Initializes a new instance of the <see cref="DiskUsageMonitor"/> class.
/// </summary>
/// <param name="driveName">Name of the drive.</param>
internal DiskUsageMonitor(string driveName)
{
// Get a list of the counters and look for "C:"
var perfCategory = new PerformanceCounterCategory("PhysicalDisk");
string[] instanceNames = perfCategory.GetInstanceNames();
foreach (string name in instanceNames)
{
if (name.IndexOf("C:") > 0)
{
if (string.IsNullOrEmpty(driveName))
driveName = name;
}
}
_readBytesCounter = new PerformanceCounter("PhysicalDisk", "Disk Read Bytes/sec", driveName);
_writeBytesCounter = new PerformanceCounter("PhysicalDisk", "Disk Write Bytes/sec", driveName);
_diskQueueCounter = new PerformanceCounter("PhysicalDisk", "Current Disk Queue Length", driveName);
_idleCounter = new PerformanceCounter("PhysicalDisk", "% Idle Time", driveName);
InitTimer();
}
/// <summary>
/// Default constructor
/// </summary>
internal DiskUsageMonitor()
: this(null)
{
}
/// <summary>
/// Gets or sets the update interval milli secs.
/// </summary>
/// <value>The update interval milli secs.</value>
public int UpdateIntervalMilliSecs
{
get { return _updateResolutionMillisecs; }
set
{
_updateResolutionMillisecs = value;
InitTimer();
}
}
internal event DiskUsageResultHander DiskUsageResult;
/// <summary>
/// Inits the timer.
/// </summary>
private void InitTimer()
{
StopTimer();
_perfTimer = new Timer(_updateResolutionMillisecs);
_perfTimer.Elapsed += PerfTimerElapsed;
_perfTimer.Start();
}
/// <summary>
/// Performance counter timer elapsed event handler
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.Timers.ElapsedEventArgs"/> instance containing the event data.</param>
private void PerfTimerElapsed(object sender, ElapsedEventArgs e)
{
float diskReads = _readBytesCounter.NextValue();
float diskWrites = _writeBytesCounter.NextValue();
float diskQueue = _diskQueueCounter.NextValue();
float idlePercent = _idleCounter.NextValue();
if (idlePercent > 100)
{
idlePercent = 100;
}
if (DiskUsageResult != null)
{
var stats = new DiskUsageStats
{
DriveName = _readBytesCounter.InstanceName,
DiskQueueLength = (int)diskQueue,
ReadBytesPerSec = (int)diskReads,
WriteBytesPerSec = (int)diskWrites,
DiskUsagePercent = 100 - (int)idlePercent
};
DiskUsageResult(stats);
}
}
/// <summary>
/// Stops the timer.
/// </summary>
internal void StopTimer() {
if (_perfTimer != null) {
try {
_perfTimer.Stop();
} catch {
} finally {
_perfTimer.Close();
_perfTimer.Dispose();
_perfTimer.Elapsed -= PerfTimerElapsed;
_perfTimer = null;
}
}
}
/// <summary>
/// Closes this instance.
/// </summary>
internal void Close()
{
StopTimer();
DiskUsageResult = null;
}
#region Nested type: DiskUsageResultHander
internal delegate void DiskUsageResultHander(DiskUsageStats diskStats);
#endregion
}