如何获取全局 Windows I/O 统计信息?
How to get global Windows I/O statistics?
有一个 WinAPI 函数 GetProcessIoCounters
提供给定进程的所有 I/O 操作的详细信息:read/write 操作的数量和字节数 read/written 自过程开始了。任务管理器很可能使用此功能来显示这些数字:
是否有一种相对简单的方法来获得相同或相似的统计数据,但对于 整个系统 自启动以来?
请注意,这与枚举所有当前进程并汇总 GetProcessIoCounters
的结果不同,因为有些进程启动,运行 一段时间并完成。在我调用 GetProcessIoCounters
时,这样的进程不再存在,但我想知道系统的整体 I/O。
我打算每隔一小时左右收集一次这些统计数据,并将它们记录到数据库中以供将来分析和帮助调试。
我正在寻找一种适用于没有 WMI 的 Windows XP 的方法(我们使用显着缩减的 Windows XP Embedded),但是如果这种方法仅适用于更高版本的Windows,请分享。总有一天会有用的。
更新
我尝试了Jerry Coffin建议的DeviceIoControl(IOCTL_DISK_PERFORMANCE)
方法。
我必须 运行 diskperf.exe -Y
才能让它发挥作用。我什至不必重新启动,但如果没有它,DeviceIoControl
会因 GetLastError=31
而失败(连接到系统的设备无法正常工作。) DeviceIoControl
在重新启动后继续工作而无需再次调用 运行 diskperf.exe -Y
,但重新启动后第一次调用 DeviceIoControl
在所有字段(BytesRead、BytesWritten、ReadCount、WriteCount)中返回零。进一步的调用返回非零统计数据。很明显,系统启动时有大量磁盘activity,但没有统计。所以,看起来重启后第一次调用 DeviceIoControl
确实是 enables/starts 计数器。
如果我 运行 diskperf.exe -N
,那么 DeviceIoControl
会立即停止工作而无需重新启动。当我 运行 diskperf.exe -Y
时,DeviceIoControl
再次正常工作。
现在的问题是:diskperf.exe -Y
做什么以及如何在我的程序中做同样的事情?
更新 2
我在 diskperf.exe -Y
之后和 diskperf.exe -N
之后导出了整个注册表并寻找差异。我能找到的唯一区别是一个键:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\PartMgr
"EnableCounterForIoctl"=dword:00000001
diskperf.exe -Y
添加此密钥,diskperf.exe -N
删除它。
我试过add/delete这个键值直接进入注册表。
如果密钥不存在且 DeviceIoControl
不起作用(在我 运行 diskperf.exe -N
之后),我将像这样添加此密钥:
reg add "HKLM\SYSTEM\CurrentControlSet\Services\PartMgr" /v EnableCounterForIoctl /t REG_DWORD /d 1
,然后DeviceIoControl
立即开始工作。
如果密钥存在并且 DeviceIoControl
有效(在我 运行 diskperf.exe -Y
之后),我会像这样删除此密钥:
reg delete "HKLM\SYSTEM\CurrentControlSet\Services\PartMgr" /v EnableCounterForIoctl
然后 DeviceIoControl
继续工作,返回的统计数据不断增长。直到重启。
diskperf.exe
除了更改注册表值外,还必须执行其他操作,例如强制注册表的 flush/refresh。在我的例子中,我关心启用这些计数器,它似乎可以通过简单地添加注册表项来工作。
您可以使用 DeviceIoControl 一次获取一个磁盘的数据,如下所示:
#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";
}
例如,现在在我的机器上显示:
Bytes read: 15,768,173,568
Bytes written: 22,370,663,424
根据实验,我得到的结果看起来很合理。例如,插入闪存驱动器并打开其中一些图片的预览后,我得到:
Bytes read: 3,956,736
Bytes written: 0
要获取系统中当前可见的所有驱动器的数据,添加对GetLogicalDrives
或GetLogicalDriveStrings
的调用,并在循环中调用这样的代码,但填写相应的驱动器号每次调用。
这仍然不能保证是自系统启动以来的所有数据。例如,如果您弹出一个可移动磁盘,有关读取到该驱动器的内容 from/written 的信息将丢失。如果将其重新插入,您将只会获得自上次插入后 read/written 的数据。
我完全不确定你是否能够做得比这更好,至少不需要做大量的额外工作来定期收集数据并跟踪磁盘何时可用弹出和插入等。当一个磁盘被弹出时,我怀疑 Windows 几乎丢弃了关于该驱动器的所有统计数据,所以如果它稍后重新插入,Windows 不再 有 关于弹射前用它做了什么的统计数据。
同样,如果磁盘已弹出,因此当前不可见,则无法打开它,因此也无法检索有关该驱动器的任何统计信息。
有一个 WinAPI 函数 GetProcessIoCounters
提供给定进程的所有 I/O 操作的详细信息:read/write 操作的数量和字节数 read/written 自过程开始了。任务管理器很可能使用此功能来显示这些数字:
是否有一种相对简单的方法来获得相同或相似的统计数据,但对于 整个系统 自启动以来?
请注意,这与枚举所有当前进程并汇总 GetProcessIoCounters
的结果不同,因为有些进程启动,运行 一段时间并完成。在我调用 GetProcessIoCounters
时,这样的进程不再存在,但我想知道系统的整体 I/O。
我打算每隔一小时左右收集一次这些统计数据,并将它们记录到数据库中以供将来分析和帮助调试。
我正在寻找一种适用于没有 WMI 的 Windows XP 的方法(我们使用显着缩减的 Windows XP Embedded),但是如果这种方法仅适用于更高版本的Windows,请分享。总有一天会有用的。
更新
我尝试了Jerry Coffin建议的DeviceIoControl(IOCTL_DISK_PERFORMANCE)
方法。
我必须 运行 diskperf.exe -Y
才能让它发挥作用。我什至不必重新启动,但如果没有它,DeviceIoControl
会因 GetLastError=31
而失败(连接到系统的设备无法正常工作。) DeviceIoControl
在重新启动后继续工作而无需再次调用 运行 diskperf.exe -Y
,但重新启动后第一次调用 DeviceIoControl
在所有字段(BytesRead、BytesWritten、ReadCount、WriteCount)中返回零。进一步的调用返回非零统计数据。很明显,系统启动时有大量磁盘activity,但没有统计。所以,看起来重启后第一次调用 DeviceIoControl
确实是 enables/starts 计数器。
如果我 运行 diskperf.exe -N
,那么 DeviceIoControl
会立即停止工作而无需重新启动。当我 运行 diskperf.exe -Y
时,DeviceIoControl
再次正常工作。
现在的问题是:diskperf.exe -Y
做什么以及如何在我的程序中做同样的事情?
更新 2
我在 diskperf.exe -Y
之后和 diskperf.exe -N
之后导出了整个注册表并寻找差异。我能找到的唯一区别是一个键:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\PartMgr
"EnableCounterForIoctl"=dword:00000001
diskperf.exe -Y
添加此密钥,diskperf.exe -N
删除它。
我试过add/delete这个键值直接进入注册表。
如果密钥不存在且 DeviceIoControl
不起作用(在我 运行 diskperf.exe -N
之后),我将像这样添加此密钥:
reg add "HKLM\SYSTEM\CurrentControlSet\Services\PartMgr" /v EnableCounterForIoctl /t REG_DWORD /d 1
,然后DeviceIoControl
立即开始工作。
如果密钥存在并且 DeviceIoControl
有效(在我 运行 diskperf.exe -Y
之后),我会像这样删除此密钥:
reg delete "HKLM\SYSTEM\CurrentControlSet\Services\PartMgr" /v EnableCounterForIoctl
然后 DeviceIoControl
继续工作,返回的统计数据不断增长。直到重启。
diskperf.exe
除了更改注册表值外,还必须执行其他操作,例如强制注册表的 flush/refresh。在我的例子中,我关心启用这些计数器,它似乎可以通过简单地添加注册表项来工作。
您可以使用 DeviceIoControl 一次获取一个磁盘的数据,如下所示:
#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";
}
例如,现在在我的机器上显示:
Bytes read: 15,768,173,568
Bytes written: 22,370,663,424
根据实验,我得到的结果看起来很合理。例如,插入闪存驱动器并打开其中一些图片的预览后,我得到:
Bytes read: 3,956,736
Bytes written: 0
要获取系统中当前可见的所有驱动器的数据,添加对GetLogicalDrives
或GetLogicalDriveStrings
的调用,并在循环中调用这样的代码,但填写相应的驱动器号每次调用。
这仍然不能保证是自系统启动以来的所有数据。例如,如果您弹出一个可移动磁盘,有关读取到该驱动器的内容 from/written 的信息将丢失。如果将其重新插入,您将只会获得自上次插入后 read/written 的数据。
我完全不确定你是否能够做得比这更好,至少不需要做大量的额外工作来定期收集数据并跟踪磁盘何时可用弹出和插入等。当一个磁盘被弹出时,我怀疑 Windows 几乎丢弃了关于该驱动器的所有统计数据,所以如果它稍后重新插入,Windows 不再 有 关于弹射前用它做了什么的统计数据。
同样,如果磁盘已弹出,因此当前不可见,则无法打开它,因此也无法检索有关该驱动器的任何统计信息。