Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get global Windows I/O statistics?

There is a WinAPI function GetProcessIoCounters that provides details of all I/O operations of a given process: number of read/write operations and number of bytes read/written since the process started. Most likely Task Manager uses this function to show these numbers:

Task Manager


Is there a relatively easy way to get the same or similar stats, but for the whole system since it started?

Please note, that it is not the same as enumerate all current processes and sum up results of GetProcessIoCounters, because there are processes that start, run for a while and finish. At the time when I call GetProcessIoCounters such processes don't exist anymore, but I would like to know the overall I/O of the system.

I intend to collect these stats every hour or so and log them into the database for future analysis and to help with debugging.

I'm looking for a method that works on Windows XP without WMI (we use a significantly cut-down Windows XP Embedded), but if such method exists only for later versions of Windows, please share it. Eventually it will be useful.

Update

I tried the DeviceIoControl(IOCTL_DISK_PERFORMANCE) method suggested by Jerry Coffin.

I had to run diskperf.exe -Y to make it work. I didn't even have to reboot, but without it DeviceIoControl was failing with GetLastError=31 (A device attached to the system is not functioning.) DeviceIoControl continued to work after reboot without the need to run diskperf.exe -Y again, but the first call to DeviceIoControl after reboot returned zeros in all fields (BytesRead, BytesWritten, ReadCount, WriteCount). Further calls returned non-zero stats. Obviously, there was some substantial disk activity when the system started, but it was not counted. So, it looks like that the first call to DeviceIoControl after reboot indeed enables/starts the counters.

If I run diskperf.exe -N, then DeviceIoControl stops working immediately without the need to reboot. When I run diskperf.exe -Y, then DeviceIoControl works fine again.

Now the question is: what diskperf.exe -Y does and how to do the same thing in my program?

Update 2

I exported the whole registry after diskperf.exe -Y and after diskperf.exe -N and looked for the difference. The only difference I could find was in one key:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\PartMgr
"EnableCounterForIoctl"=dword:00000001

diskperf.exe -Y adds this key, diskperf.exe -N removes it.

I tried to add/delete this key directly into the registry.


If the key doesn't exist and DeviceIoControl doesn't work (after I run diskperf.exe -N) and I add this key like this:

reg add "HKLM\SYSTEM\CurrentControlSet\Services\PartMgr" /v EnableCounterForIoctl /t REG_DWORD /d 1

, then DeviceIoControl begins to work immediately.

If the key exists and DeviceIoControl works (after I run diskperf.exe -Y) and I delete this key like this:

reg delete "HKLM\SYSTEM\CurrentControlSet\Services\PartMgr" /v EnableCounterForIoctl

then DeviceIoControl continues to work and the returned statistics keeps growing. Until reboot.

diskperf.exe must be doing something else in addition to changing the registry value, something like forcing the flush/refresh of the registry. In my case I care about enabling these counters and it seems to work with the simple adding of the registry key.

like image 986
Vladimir Baranov Avatar asked May 26 '15 05:05

Vladimir Baranov


1 Answers

You can get the data one disk at a time using DeviceIoControl, something like this:

#include <windows.h>
#include <iostream>

int main() { 
    HANDLE dev = CreateFile("\\\\.\\C:", 
        FILE_READ_ATTRIBUTES, 
        FILE_SHARE_READ | FILE_SHARE_WRITE, 
        NULL, 
        OPEN_EXISTING, 
        0, 
        NULL);

    DISK_PERFORMANCE disk_info { };
    DWORD bytes;

    if (dev == INVALID_HANDLE_VALUE) {
        std::cerr << "Error opening disk\n";
        return 1;
    }

    if (!DeviceIoControl(dev, 
            IOCTL_DISK_PERFORMANCE, 
            NULL, 
            0, 
            &disk_info, 
            sizeof(disk_info), 
            &bytes, 
            NULL))
    {
        std::cerr << "Failure in DeviceIoControl\n";
        return 1;
    }

    std::cout.imbue(std::locale(""));
    std::cout << "Bytes read: " << disk_info.BytesRead.QuadPart << "\n";
    std::cout << "Bytes written: " << disk_info.BytesWritten.QuadPart << "\n";
}

For example, on my machine right now, this shows:

Bytes read: 15,768,173,568
Bytes written: 22,370,663,424

Experimentally, the results I'm getting look reasonable. For example, after plugging in a flash drive and opening a preview of some pictures on it, I get:

Bytes read: 3,956,736
Bytes written: 0

To get the data for all drives currently visible in the system, add a call to GetLogicalDrives or GetLogicalDriveStrings, and call code like this in a loop, but filling in the appropriate drive letter for each call.

This still isn't guaranteed to be all the data since the system was booted though. For example, if you eject a removable disk, information about what was read from/written to that drive is lost. If you plug it back in, you'll only get data about what was read/written since the last time it was plugged in.

I'm not at all sure you're going to be able to do a whole lot better than that though, at least without doing a fair amount of extra work to collect data periodically and keep track of when disks are ejected and inserted and such. When a disk is ejected, I suspect Windows pretty much throws away all the statistics about that drive, so if it's later plugged back in, Windows no longer has stats about what was done with it prior to ejection.

Likewise, if a disk has been ejected so it's not currently visible, this won't be able to open it to start with, so it won't be able to retrieve any stats about that drive either.

like image 155
Jerry Coffin Avatar answered Sep 24 '22 21:09

Jerry Coffin