commit bdc2295ee46e92a92df085025cc15e00e5278393 Author: KoDer Date: Fri May 29 13:04:54 2026 +0700 Добавлена папка source в CristalDiskMark diff --git a/CristalDiskMark/source/CrystalDiskMark/AboutDlg.cpp b/CristalDiskMark/source/CrystalDiskMark/AboutDlg.cpp new file mode 100644 index 0000000..deaf8d3 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/AboutDlg.cpp @@ -0,0 +1,279 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "DiskMark.h" +#include "DiskMarkDlg.h" +#include "AboutDlg.h" + +IMPLEMENT_DYNCREATE(CAboutDlg, CDialog) + +CAboutDlg::CAboutDlg(CWnd* pParent /*=NULL*/) + : CDialogFx(CAboutDlg::IDD, pParent) +{ + CMainDialogFx* p = (CMainDialogFx*)pParent; + + m_ZoomType = p->GetZoomType(); + m_FontScale = p->GetFontScale(); + m_FontRatio = p->GetFontRatio(); + m_FontFace = p->GetFontFace(); + m_FontRender = p->GetFontRender(); + m_CurrentLangPath = p->GetCurrentLangPath(); + m_DefaultLangPath = p->GetDefaultLangPath(); + m_ThemeDir = p->GetThemeDir(); + m_CurrentTheme = p->GetCurrentTheme(); + m_DefaultTheme = p->GetDefaultTheme(); + m_Ini = p->GetIniPath(); + +#ifdef SUISHO_SHIZUKU_SUPPORT + m_BackgroundName = L"About"; +#else + m_BackgroundName = L""; +#endif +} + +CAboutDlg::~CAboutDlg() +{ +} + +void CAboutDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialogFx::DoDataExchange(pDX); + DDX_Control(pDX, IDC_LOGO, m_CtrlLogo); + + DDX_Control(pDX, IDC_PROJECT_SITE_1, m_CtrlProjectSite1); + DDX_Control(pDX, IDC_PROJECT_SITE_2, m_CtrlProjectSite2); + DDX_Control(pDX, IDC_PROJECT_SITE_3, m_CtrlProjectSite3); + DDX_Control(pDX, IDC_PROJECT_SITE_4, m_CtrlProjectSite4); + DDX_Control(pDX, IDC_PROJECT_SITE_5, m_CtrlProjectSite5); + + DDX_Control(pDX, IDC_VERSION, m_CtrlVersion); + DDX_Control(pDX, IDC_LICENSE, m_CtrlLicense); + DDX_Control(pDX, IDC_RELEASE, m_CtrlRelease); + DDX_Control(pDX, IDC_COPYRIGHT1, m_CtrlCopyright1); + DDX_Control(pDX, IDC_COPYRIGHT2, m_CtrlCopyright2); + DDX_Control(pDX, IDC_COPYRIGHT3, m_CtrlCopyright3); + DDX_Control(pDX, IDC_EDITION, m_CtrlEdition); +} + +BOOL CAboutDlg::OnInitDialog() +{ + CDialogFx::OnInitDialog(); + + SetWindowTitle(i18n(L"WindowTitle", L"ABOUT")); + + m_bShowWindow = TRUE; + m_CtrlVersion.SetWindowTextW(PRODUCT_NAME L" " PRODUCT_VERSION); + m_CtrlEdition.SetWindowTextW(PRODUCT_EDITION); + m_CtrlRelease.SetWindowTextW(L"Release: " PRODUCT_RELEASE); + m_CtrlCopyright1.SetWindowTextW(PRODUCT_COPYRIGHT_1); + m_CtrlCopyright2.SetWindowTextW(PRODUCT_COPYRIGHT_2); + m_CtrlCopyright3.SetWindowTextW(PRODUCT_COPYRIGHT_3); + m_CtrlLicense.SetWindowTextW(PRODUCT_LICENSE); + + UpdateDialogSize(); + + CenterWindow(); + ShowWindow(SW_SHOW); + return TRUE; +} + +BEGIN_MESSAGE_MAP(CAboutDlg, CDialogFx) + ON_BN_CLICKED(IDC_LOGO, &CAboutDlg::OnLogo) + ON_BN_CLICKED(IDC_LICENSE, &CAboutDlg::OnLicense) + ON_BN_CLICKED(IDC_VERSION, &CAboutDlg::OnVersion) +#ifdef SUISHO_SHIZUKU_SUPPORT + ON_BN_CLICKED(IDC_PROJECT_SITE_1, &CAboutDlg::OnProjectSite1) + ON_BN_CLICKED(IDC_PROJECT_SITE_2, &CAboutDlg::OnProjectSite2) + ON_BN_CLICKED(IDC_PROJECT_SITE_3, &CAboutDlg::OnProjectSite3) + ON_BN_CLICKED(IDC_PROJECT_SITE_4, &CAboutDlg::OnProjectSite4) + ON_BN_CLICKED(IDC_PROJECT_SITE_5, &CAboutDlg::OnProjectSite5) +#endif +END_MESSAGE_MAP() + + +void CAboutDlg::UpdateDialogSize() +{ + CDialogFx::UpdateDialogSize(); + m_bHighContrast = FALSE; + + ChangeZoomType(m_ZoomType); + SetClientSize(SIZE_X, SIZE_Y, m_ZoomRatio); + UpdateBackground(TRUE, m_bDarkMode); + +#ifdef SUISHO_AOI_SUPPORT + m_CtrlLogo.InitControl(32, 484, 128, 144, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlSecretVoice.InitControl(392, 288, 60, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlProjectSite1.InitControl(184, 508, 148, 16, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlProjectSite2.InitControl(244, 540, 108, 16, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlProjectSite3.InitControl(232, 556, 180, 16, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlProjectSite4.InitControl(244, 576, 120, 16, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlProjectSite5.InitControl(0, 0, 0, 0, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlSecretVoice.SetHandCursor(); + m_CtrlProjectSite1.SetHandCursor(); + m_CtrlProjectSite2.SetHandCursor(); + m_CtrlProjectSite3.SetHandCursor(); + m_CtrlProjectSite4.SetHandCursor(); + m_CtrlProjectSite5.SetHandCursor(); + +#elif MSI_MEI_SUPPORT + m_CtrlProjectSite1.InitControl(24, 460, 348, 128, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlProjectSite2.InitControl(168, 604, 36, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlProjectSite3.InitControl(332, 604, 104, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlProjectSite4.InitControl(20, 20, 120, 40, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlProjectSite5.InitControl(464, 604, 168, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlLogo.InitControl(80, 64, 128, 128, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Logo"), 1, BS_CENTER, OwnerDrawImage, FALSE, FALSE, FALSE); + m_CtrlProjectSite1.SetHandCursor(); + m_CtrlProjectSite2.SetHandCursor(); + m_CtrlProjectSite3.SetHandCursor(); + m_CtrlProjectSite4.SetHandCursor(); + m_CtrlProjectSite5.SetHandCursor(); + +#elif SUISHO_SHIZUKU_SUPPORT + m_CtrlProjectSite1.InitControl(64, 372, 140, 16, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlProjectSite2.InitControl(64, 416, 148, 16, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlProjectSite3.InitControl(64, 432, 184, 16, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlProjectSite4.InitControl(40, 460, 208, 16, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlProjectSite5.InitControl(92, 504, 432, 124, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlLogo.InitControl(80, 12, 128, 128, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Logo"), 1, BS_CENTER, OwnerDrawImage, FALSE, FALSE, FALSE); + m_CtrlProjectSite1.SetHandCursor(); + m_CtrlProjectSite2.SetHandCursor(); + m_CtrlProjectSite3.SetHandCursor(); + m_CtrlProjectSite4.SetHandCursor(); + m_CtrlProjectSite5.SetHandCursor(); +#else + m_CtrlLogo.InitControl(20, 20, 128, 128, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Logo"), 1, BS_CENTER, OwnerDrawImage, FALSE, FALSE, FALSE); + m_CtrlProjectSite1.ShowWindow(SW_HIDE); + m_CtrlProjectSite2.ShowWindow(SW_HIDE); + m_CtrlProjectSite3.ShowWindow(SW_HIDE); + m_CtrlProjectSite4.ShowWindow(SW_HIDE); + m_CtrlProjectSite5.ShowWindow(SW_HIDE); +#endif + + m_CtrlLogo.SetHandCursor(); + +#ifdef MSI_MEI_SUPPORT + COLORREF fontColor = RGB(255, 255, 255); +#else + COLORREF fontColor = RGB(0, 0, 0); +#endif + + m_CtrlVersion.SetFontEx(m_FontFace, 20, 20, m_ZoomRatio, m_FontRatio, fontColor, FW_BOLD, m_FontRender); + m_CtrlEdition.SetFontEx(m_FontFace, 20, 20, m_ZoomRatio, m_FontRatio, fontColor, FW_BOLD, m_FontRender); + m_CtrlRelease.SetFontEx(m_FontFace, 16, 16, m_ZoomRatio, m_FontRatio, fontColor, FW_NORMAL, m_FontRender); + m_CtrlCopyright1.SetFontEx(m_FontFace, 16, 16, m_ZoomRatio, m_FontRatio, fontColor, FW_NORMAL, m_FontRender); + m_CtrlCopyright2.SetFontEx(m_FontFace, 16, 16, m_ZoomRatio, m_FontRatio, fontColor, FW_NORMAL, m_FontRender); + m_CtrlCopyright3.SetFontEx(m_FontFace, 16, 16, m_ZoomRatio, m_FontRatio, fontColor, FW_NORMAL, m_FontRender); + m_CtrlLicense.SetFontEx(m_FontFace, 16, 16, m_ZoomRatio, m_FontRatio, fontColor, FW_NORMAL, m_FontRender); + + m_CtrlVersion.SetHandCursor(); + m_CtrlLicense.SetHandCursor(); + +#ifdef SUISHO_AOI_SUPPORT + m_CtrlVersion.InitControl(0, 136, 288, 28, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlEdition.InitControl(0, 164, 288, 28, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlRelease.InitControl(0, 200, 288, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlCopyright1.InitControl(0, 220, 288, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlCopyright2.InitControl(0, 240, 288, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlCopyright3.InitControl(0, 260, 288, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlLicense.InitControl(0, 280, 288, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + +#elif MSI_MEI_SUPPORT + m_CtrlVersion.InitControl(0, 204, 288, 28, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlEdition.InitControl(0, 232, 288, 28, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlRelease.InitControl(0, 268, 288, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlCopyright1.InitControl(0, 288, 288, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlCopyright2.InitControl(0, 308, 288, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlCopyright3.InitControl(0, 328, 288, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlLicense.InitControl(0, 0, 0, 0, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + +#elif SUISHO_SHIZUKU_SUPPORT + m_CtrlVersion.InitControl(0, 152, 288, 28, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlEdition.InitControl(0, 180, 288, 28, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlRelease.InitControl(0, 216, 288, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlCopyright1.InitControl(0, 236, 288, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlCopyright2.InitControl(0, 256, 288, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlCopyright3.InitControl(0, 276, 288, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + m_CtrlLicense.InitControl(0, 296, 288, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, FALSE, FALSE, FALSE); + +#else + m_CtrlVersion.InitControl(160, 12, 364, 28, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, FALSE); + m_CtrlEdition.InitControl(160, 40, 364, 28, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, FALSE); + m_CtrlRelease.InitControl(160, 76, 364, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, FALSE); + m_CtrlCopyright1.InitControl(160, 96, 364, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, FALSE); + m_CtrlCopyright2.InitControl(160, 116, 364, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, FALSE); + m_CtrlCopyright3.InitControl(160, 136, 364, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, FALSE); + m_CtrlLicense.InitControl(160, 136, 364, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, FALSE); + m_CtrlCopyright3.ShowWindow(SW_HIDE); + +#endif + + Invalidate(); +} + +void CAboutDlg::OnLogo() +{ + if (GetUserDefaultLCID() == 0x0411)// Japanese + { + OpenUrl(URL_MAIN_JA); + } + else // Other Language + { + OpenUrl(URL_MAIN_EN); + } +} + +void CAboutDlg::OnVersion() +{ + if (GetUserDefaultLCID() == 0x0411)// Japanese + { + OpenUrl(URL_VERSION_JA); + } + else // Other Language + { + OpenUrl(URL_VERSION_EN); + } + +} +void CAboutDlg::OnLicense() +{ + if (GetUserDefaultLCID() == 0x0411)// Japanese + { + OpenUrl(URL_LICENSE_JA); + } + else // Other Language + { + OpenUrl(URL_LICENSE_EN); + } +} + +#ifdef SUISHO_SHIZUKU_SUPPORT +void CAboutDlg::OnProjectSite1() +{ + OpenUrl(URL_PROJECT_SITE_1); +} + +void CAboutDlg::OnProjectSite2() +{ + OpenUrl(URL_PROJECT_SITE_2); +} + +void CAboutDlg::OnProjectSite3() +{ + OpenUrl(URL_PROJECT_SITE_3); +} + +void CAboutDlg::OnProjectSite4() +{ + OpenUrl(URL_PROJECT_SITE_4); +} + +void CAboutDlg::OnProjectSite5() +{ + OpenUrl(URL_PROJECT_SITE_5); +} +#endif \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/AboutDlg.h b/CristalDiskMark/source/CrystalDiskMark/AboutDlg.h new file mode 100644 index 0000000..93809ee --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/AboutDlg.h @@ -0,0 +1,67 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once +#include "DialogFx.h" +#include "StaticFx.h" +#include "ButtonFx.h" + +class CAboutDlg : public CDialogFx +{ + DECLARE_DYNCREATE(CAboutDlg) + +#ifdef SUISHO_AOI_SUPPORT + static const int SIZE_X = 640; + static const int SIZE_Y = 640; +#elif MSI_MEI_SUPPORT + static const int SIZE_X = 640; + static const int SIZE_Y = 640; +#elif SUISHO_SHIZUKU_SUPPORT + static const int SIZE_X = 640; + static const int SIZE_Y = 660; +#else + static const int SIZE_X = 540; + static const int SIZE_Y = 168; +#endif + +public: + CAboutDlg(CWnd* pParent = NULL); + virtual ~CAboutDlg(); + + enum { IDD = IDD_ABOUT }; + +protected: + virtual void DoDataExchange(CDataExchange* pDX); + virtual BOOL OnInitDialog(); + virtual void UpdateDialogSize(); + + DECLARE_MESSAGE_MAP() + afx_msg void OnLogo(); + afx_msg void OnVersion(); + afx_msg void OnLicense(); + afx_msg void OnProjectSite1(); + afx_msg void OnProjectSite2(); + afx_msg void OnProjectSite3(); + afx_msg void OnProjectSite4(); + afx_msg void OnProjectSite5(); + + CButtonFx m_CtrlLogo; + CButtonFx m_CtrlSecretVoice; + CButtonFx m_CtrlProjectSite1; + CButtonFx m_CtrlProjectSite2; + CButtonFx m_CtrlProjectSite3; + CButtonFx m_CtrlProjectSite4; + CButtonFx m_CtrlProjectSite5; + CButtonFx m_CtrlVersion; + CButtonFx m_CtrlLicense; + + CStaticFx m_CtrlEdition; + CStaticFx m_CtrlRelease; + CStaticFx m_CtrlCopyright1; + CStaticFx m_CtrlCopyright2; + CStaticFx m_CtrlCopyright3; +}; diff --git a/CristalDiskMark/source/CrystalDiskMark/DeclareDPIAware.manifest b/CristalDiskMark/source/CrystalDiskMark/DeclareDPIAware.manifest new file mode 100644 index 0000000..76a25be --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/DeclareDPIAware.manifest @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + True/PM + PerMonitorV2, PerMonitor + + + diff --git a/CristalDiskMark/source/CrystalDiskMark/DiskBench.cpp b/CristalDiskMark/source/CrystalDiskMark/DiskBench.cpp new file mode 100644 index 0000000..cb0fdac --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/DiskBench.cpp @@ -0,0 +1,935 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "DiskMark.h" +#include "DiskMarkDlg.h" +#include "DiskBench.h" + +#include +#include +#include +#pragma comment(lib,"winmm.lib") + +#pragma warning(disable : 4996) + +static CString TestFilePath; +static CString TestFileDir; +static CString DiskSpdExe; + +static HANDLE hFile; +static int DiskTestCount; +static UINT64 DiskTestSize; +static int BenchType[9]; +static int BenchSize[9]; +static int BenchQueues[9]; +static int BenchThreads[9]; +// static int Affinity; +static BOOL MixMode; +static int MixRatio; + +static void ShowErrorMessage(CString message); +static void Interval(void* dlg); + +static BOOL Init(void* dlg); +static void DiskSpd(void* dlg, DISK_SPD_CMD cmd); + +static UINT Exit(void* dlg); +static void CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime); +static volatile BOOL WaitFlag; + +#define DISK_SPD_EXE_32 L"CdmResource\\diskspd\\diskspd32.exe" +#define DISK_SPD_EXE_64 L"CdmResource\\diskspd\\diskspd64.exe" +#define DISK_SPD_EXE_32_LEGACY L"CdmResource\\diskspd\\diskspd32L.exe" +#define DISK_SPD_EXE_64_LEGACY L"CdmResource\\diskspd\\diskspd64L.exe" +#define DISK_SPD_EXE_ARM32 L"CdmResource\\diskspd\\diskspdA32.exe" +#define DISK_SPD_EXE_ARM64 L"CdmResource\\diskspd\\diskspdA64.exe" + +PROCESS_INFORMATION pi; + +int ExecAndWait(TCHAR *pszCmd, BOOL bNoWindow, double *latency) +{ + DWORD Code = 0; + BOOL bSuccess; + STARTUPINFO si; + + memset(&si, 0, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + + if (bNoWindow) { + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + } + + CString name; + name.Format(L"CrystalDiskMark%08X", GetCurrentProcessId()); + auto size = 8; + + HANDLE hSharedMemory = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, NULL, size, name.GetString()); + if (hSharedMemory != NULL) + { + auto pMemory = (double*)MapViewOfFile(hSharedMemory, FILE_MAP_ALL_ACCESS, NULL, NULL, size); + if (pMemory != NULL) + { + bSuccess = CreateProcess(NULL, pszCmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); + if (bSuccess != TRUE) + { + UnmapViewOfFile(pMemory); + CloseHandle(hSharedMemory); + return 0; + } + + WaitForInputIdle(pi.hProcess, INFINITE); + WaitForSingleObject(pi.hProcess, INFINITE); + GetExitCodeProcess(pi.hProcess, &Code); + + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + + pi.hProcess = NULL; + + *latency = (double)*pMemory * 1000; // milli sec to micro sec + + UnmapViewOfFile(pMemory); + CloseHandle(hSharedMemory); + } + } + + return Code; +} + + +void ShowErrorMessage(CString message) +{ + DWORD lastErrorCode = GetLastError(); + CString errorMessage; + LPVOID lpMessageBuffer; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, lastErrorCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMessageBuffer, 0, NULL); + errorMessage.Format(L"0x%08X:%s", lastErrorCode, (LPTSTR) lpMessageBuffer); + + AfxMessageBox(message + L"\r\n" + errorMessage); + LocalFree( lpMessageBuffer ); +} + +void Interval(void* dlg) +{ + int intervalTime = ((CDiskMarkDlg*) dlg)->m_IntervalTime; + CString title; + + for (int i = 0; i < intervalTime; i++) + { + if (!((CDiskMarkDlg*) dlg)->m_DiskBenchStatus) + { + return; + } + title.Format(L"Interval Time %d/%d sec", i, intervalTime); + ::PostMessage(((CDiskMarkDlg*) dlg)->GetSafeHwnd(), WM_UPDATE_MESSAGE, (WPARAM) &title, 0); + Sleep(1000); + } +} + +UINT ExecDiskBenchAll(LPVOID dlg) +{ + int benchmark = ((CDiskMarkDlg*)dlg)->m_Benchmark; + + if(Init(dlg)) + { + if (benchmark & BENCHMARK_READ) + { + DiskSpd(dlg, TEST_READ_0); Interval(dlg); + DiskSpd(dlg, TEST_READ_1); Interval(dlg); + DiskSpd(dlg, TEST_READ_2); Interval(dlg); + DiskSpd(dlg, TEST_READ_3); + } + if ((benchmark & BENCHMARK_READ) && (benchmark & BENCHMARK_WRITE)) + { + Interval(dlg); + } + if (benchmark & BENCHMARK_WRITE) + { + DiskSpd(dlg, TEST_WRITE_0); Interval(dlg); + DiskSpd(dlg, TEST_WRITE_1); Interval(dlg); + DiskSpd(dlg, TEST_WRITE_2); Interval(dlg); + DiskSpd(dlg, TEST_WRITE_3); + } + +#ifdef MIX_MODE + if (MixMode) + { + Interval(dlg); + DiskSpd(dlg, TEST_MIX_0); Interval(dlg); + DiskSpd(dlg, TEST_MIX_1); Interval(dlg); + DiskSpd(dlg, TEST_MIX_2); Interval(dlg); + DiskSpd(dlg, TEST_MIX_3); + } +#endif + } + + return Exit(dlg); +} + +UINT ExecDiskBenchAllPeak(LPVOID dlg) +{ + int benchmark = ((CDiskMarkDlg*)dlg)->m_Benchmark; + + if (Init(dlg)) + { + if (benchmark & BENCHMARK_READ) + { + DiskSpd(dlg, TEST_READ_4); Interval(dlg); + DiskSpd(dlg, TEST_READ_5); + } + if ((benchmark & BENCHMARK_READ) && (benchmark & BENCHMARK_WRITE)) + { + Interval(dlg); + } + if (benchmark & BENCHMARK_WRITE) + { + DiskSpd(dlg, TEST_WRITE_4); Interval(dlg); + DiskSpd(dlg, TEST_WRITE_5); + } + +#ifdef MIX_MODE + if (MixMode) + { + Interval(dlg); + DiskSpd(dlg, TEST_MIX_4); Interval(dlg); + DiskSpd(dlg, TEST_MIX_5); + } +#endif + } + + return Exit(dlg); +} + +UINT ExecDiskBenchAllReal(LPVOID dlg) +{ + int benchmark = ((CDiskMarkDlg*)dlg)->m_Benchmark; + + if (Init(dlg)) + { + if (benchmark & BENCHMARK_READ) + { + DiskSpd(dlg, TEST_READ_6); Interval(dlg); + DiskSpd(dlg, TEST_READ_7); + } + if ((benchmark & BENCHMARK_READ) && (benchmark & BENCHMARK_WRITE)) + { + Interval(dlg); + } + if (benchmark & BENCHMARK_WRITE) + { + DiskSpd(dlg, TEST_WRITE_6); Interval(dlg); + DiskSpd(dlg, TEST_WRITE_7); + } + +#ifdef MIX_MODE + if (MixMode) + { + Interval(dlg); + DiskSpd(dlg, TEST_MIX_6); Interval(dlg); + DiskSpd(dlg, TEST_MIX_7); + } +#endif + } + + return Exit(dlg); +} + +UINT ExecDiskBenchAllDemo(LPVOID dlg) +{ + int benchmark = ((CDiskMarkDlg*)dlg)->m_Benchmark; + + if (Init(dlg)) + { + if (benchmark & BENCHMARK_READ) + { + DiskSpd(dlg, TEST_READ_8); + } + if ((benchmark & BENCHMARK_READ) && (benchmark & BENCHMARK_WRITE)) + { + Interval(dlg); + } + if (benchmark & BENCHMARK_WRITE) + { + DiskSpd(dlg, TEST_WRITE_8); + } + } + + return Exit(dlg); +} + +UINT ExecDiskBench0(LPVOID dlg) +{ + int benchmark = ((CDiskMarkDlg*)dlg)->m_Benchmark; + + if (Init(dlg)) + { + if (benchmark & BENCHMARK_READ) + { + DiskSpd(dlg, TEST_READ_0); + } + if ((benchmark & BENCHMARK_READ) && (benchmark & BENCHMARK_WRITE)) + { + Interval(dlg); + } + if (benchmark & BENCHMARK_WRITE) + { + DiskSpd(dlg, TEST_WRITE_0); + } + +#ifdef MIX_MODE + if (MixMode) + { + Interval(dlg); + DiskSpd(dlg, TEST_MIX_0); + } +#endif + } + return Exit(dlg); +} + +UINT ExecDiskBench1(LPVOID dlg) +{ + int benchmark = ((CDiskMarkDlg*)dlg)->m_Benchmark; + + if (Init(dlg)) + { + if (benchmark & BENCHMARK_READ) + { + DiskSpd(dlg, TEST_READ_1); + } + if ((benchmark & BENCHMARK_READ) && (benchmark & BENCHMARK_WRITE)) + { + Interval(dlg); + } + if (benchmark & BENCHMARK_WRITE) + { + DiskSpd(dlg, TEST_WRITE_1); + } +#ifdef MIX_MODE + if (MixMode) + { + Interval(dlg); + DiskSpd(dlg, TEST_MIX_1); + } +#endif + } + return Exit(dlg); +} + +UINT ExecDiskBench2(LPVOID dlg) +{ + int benchmark = ((CDiskMarkDlg*)dlg)->m_Benchmark; + + if (Init(dlg)) + { + if (benchmark & BENCHMARK_READ) + { + DiskSpd(dlg, TEST_READ_2); + } + if ((benchmark & BENCHMARK_READ) && (benchmark & BENCHMARK_WRITE)) + { + Interval(dlg); + } + if (benchmark & BENCHMARK_WRITE) + { + DiskSpd(dlg, TEST_WRITE_2); + } +#ifdef MIX_MODE + if (MixMode) + { + Interval(dlg); + DiskSpd(dlg, TEST_MIX_2); + } +#endif + } + return Exit(dlg); +} + +UINT ExecDiskBench3(LPVOID dlg) +{ + int benchmark = ((CDiskMarkDlg*)dlg)->m_Benchmark; + + if (Init(dlg)) + { + if (benchmark & BENCHMARK_READ) + { + DiskSpd(dlg, TEST_READ_3); + } + if ((benchmark & BENCHMARK_READ) && (benchmark & BENCHMARK_WRITE)) + { + Interval(dlg); + } + if (benchmark & BENCHMARK_WRITE) + { + DiskSpd(dlg, TEST_WRITE_3); + } +#ifdef MIX_MODE + if (MixMode) + { + Interval(dlg); + DiskSpd(dlg, TEST_MIX_3); + } +#endif + } + return Exit(dlg); +} + +UINT ExecDiskBench4(LPVOID dlg) +{ + int benchmark = ((CDiskMarkDlg*)dlg)->m_Benchmark; + + if (Init(dlg)) + { + if (benchmark & BENCHMARK_READ) + { + DiskSpd(dlg, TEST_READ_4); + } + if ((benchmark & BENCHMARK_READ) && (benchmark & BENCHMARK_WRITE)) + { + Interval(dlg); + } + if (benchmark & BENCHMARK_WRITE) + { + DiskSpd(dlg, TEST_WRITE_4); + } +#ifdef MIX_MODE + if (MixMode) + { + Interval(dlg); + DiskSpd(dlg, TEST_MIX_4); + } +#endif + } + return Exit(dlg); +} + +UINT ExecDiskBench5(LPVOID dlg) +{ + int benchmark = ((CDiskMarkDlg*)dlg)->m_Benchmark; + + if (Init(dlg)) + { + if (benchmark & BENCHMARK_READ) + { + DiskSpd(dlg, TEST_READ_5); + } + if ((benchmark & BENCHMARK_READ) && (benchmark & BENCHMARK_WRITE)) + { + Interval(dlg); + } + if (benchmark & BENCHMARK_WRITE) + { + DiskSpd(dlg, TEST_WRITE_5); + } +#ifdef MIX_MODE + if (MixMode) + { + Interval(dlg); + DiskSpd(dlg, TEST_MIX_5); + } +#endif + } + return Exit(dlg); +} + +UINT ExecDiskBench6(LPVOID dlg) +{ + int benchmark = ((CDiskMarkDlg*)dlg)->m_Benchmark; + + if (Init(dlg)) + { + if (benchmark & BENCHMARK_READ) + { + DiskSpd(dlg, TEST_READ_6); + } + if ((benchmark & BENCHMARK_READ) && (benchmark & BENCHMARK_WRITE)) + { + Interval(dlg); + } + if (benchmark & BENCHMARK_WRITE) + { + DiskSpd(dlg, TEST_WRITE_6); + } +#ifdef MIX_MODE + if (MixMode) + { + Interval(dlg); + DiskSpd(dlg, TEST_MIX_6); + } +#endif + } + return Exit(dlg); +} + +UINT ExecDiskBench7(LPVOID dlg) +{ + int benchmark = ((CDiskMarkDlg*)dlg)->m_Benchmark; + + if (Init(dlg)) + { + if (benchmark & BENCHMARK_READ) + { + DiskSpd(dlg, TEST_READ_7); + } + if ((benchmark & BENCHMARK_READ) && (benchmark & BENCHMARK_WRITE)) + { + Interval(dlg); + } + if (benchmark & BENCHMARK_WRITE) + { + DiskSpd(dlg, TEST_WRITE_7); + } +#ifdef MIX_MODE + if (MixMode) + { + Interval(dlg); + DiskSpd(dlg, TEST_MIX_7); + } +#endif + } + return Exit(dlg); +} + +BOOL Init(void* dlg) +{ + BOOL FlagArc; + BOOL result; + static CString cstr; + TCHAR drive; + + ULARGE_INTEGER freeBytesAvailableToCaller; + ULARGE_INTEGER totalNumberOfBytes; + ULARGE_INTEGER totalNumberOfFreeBytes; + + // Init m_Ini + TCHAR *ptrEnd; + TCHAR temp[MAX_PATH]; + ::GetModuleFileName(NULL, temp, MAX_PATH); + if ((ptrEnd = _tcsrchr(temp, '\\')) != NULL) + { + *ptrEnd = '\0'; + } + + pi.hProcess = NULL; + +#ifdef _M_ARM + DiskSpdExe.Format(L"%s\\%s", temp, DISK_SPD_EXE_ARM32); +#elif _M_ARM64 + DiskSpdExe.Format(L"%s\\%s", temp, DISK_SPD_EXE_ARM64); +#elif _M_X64 + if(IsWin8orLater()) + { + DiskSpdExe.Format(L"%s\\%s", temp, DISK_SPD_EXE_64); + } + else + { + DiskSpdExe.Format(L"%s\\%s", temp, DISK_SPD_EXE_64_LEGACY); + } +#else + if (IsWin8orLater()) + { + DiskSpdExe.Format(L"%s\\%s", temp, DISK_SPD_EXE_32); + } + else + { + DiskSpdExe.Format(L"%s\\%s", temp, DISK_SPD_EXE_32_LEGACY); + } +#endif + + if (! IsFileExist(DiskSpdExe)) + { + AfxMessageBox(((CDiskMarkDlg*) dlg)->m_MesDiskSpdNotFound); + ((CDiskMarkDlg*) dlg)->m_DiskBenchStatus = FALSE; + return FALSE; + } + DiskTestCount = ((CDiskMarkDlg*) dlg)->m_IndexTestCount + 1; + + CString testSize = ((CDiskMarkDlg*)dlg)->m_ValueTestSize; + if (testSize.Find(L"M") == -1) // GiB + { + DiskTestSize = (UINT64)_tstoi(testSize) * 1024; + } + else // MiB + { + DiskTestSize = (UINT64)_tstoi(testSize); + } + + for (int i = 0; i < 9; i++) + { + BenchType[i] = ((CDiskMarkDlg*)dlg)->m_BenchType[i]; + BenchSize[i] = ((CDiskMarkDlg*)dlg)->m_BenchSize[i]; + BenchQueues[i] = ((CDiskMarkDlg*)dlg)->m_BenchQueues[i]; + BenchThreads[i] = ((CDiskMarkDlg*)dlg)->m_BenchThreads[i]; + } + + MixMode = ((CDiskMarkDlg*)dlg)->m_MixMode; + MixRatio = ((CDiskMarkDlg*)dlg)->m_MixRatio; + + CString RootPath; + if(((CDiskMarkDlg*)dlg)->m_MaxIndexTestDrive != ((CDiskMarkDlg*)dlg)->m_IndexTestDrive) + { + + drive = ((CDiskMarkDlg*)dlg)->m_ValueTestDrive.GetAt(0); + cstr.Format(L"%C:\\", drive); + GetDiskFreeSpaceEx(cstr, &freeBytesAvailableToCaller, &totalNumberOfBytes, &totalNumberOfFreeBytes); + if (totalNumberOfBytes.QuadPart < ((ULONGLONG)8 * 1024 * 1024 * 1024)) // < 8 GB + { + ((CDiskMarkDlg*)dlg)->m_TestDriveInfo.Format(L"%C: %.1f%% (%.1f/%.1f MiB)", drive, + (double)(totalNumberOfBytes.QuadPart - totalNumberOfFreeBytes.QuadPart) / (double)totalNumberOfBytes.QuadPart * 100, + (totalNumberOfBytes.QuadPart - totalNumberOfFreeBytes.QuadPart) / 1024 / 1024.0, + totalNumberOfBytes.QuadPart / 1024 / 1024.0); + } + else + { + ((CDiskMarkDlg*)dlg)->m_TestDriveInfo.Format(L"%C: %.1f%% (%.1f/%.1f GiB)", drive, + (double)(totalNumberOfBytes.QuadPart - totalNumberOfFreeBytes.QuadPart) / (double)totalNumberOfBytes.QuadPart * 100, + (totalNumberOfBytes.QuadPart - totalNumberOfFreeBytes.QuadPart) / 1024 / 1024 / 1024.0, + totalNumberOfBytes.QuadPart / 1024 / 1024 / 1024.0); + } + RootPath.Format(L"%c:\\", drive); + } + else + { + RootPath = ((CDiskMarkDlg*)dlg)->m_TestTargetPath; + RootPath += L"\\"; + } + + TestFileDir.Format(L"%sCrystalDiskMark%08X", (LPTSTR)RootPath.GetString(), timeGetTime()); + CreateDirectory(TestFileDir, NULL); + TestFilePath.Format(L"%s\\CrystalDiskMark%08X.tmp", (LPTSTR)TestFileDir.GetString(), timeGetTime()); + + DWORD FileSystemFlags; + GetVolumeInformation(RootPath, NULL, NULL, NULL, NULL, &FileSystemFlags, NULL, NULL); + if(FileSystemFlags & FS_VOL_IS_COMPRESSED) + { + FlagArc = TRUE; + } + else + { + FlagArc = FALSE; + } + +// Check Disk Capacity // + OSVERSIONINFO osVersionInfo; + osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&osVersionInfo); + + ULARGE_INTEGER FreeBytesAvailableToCaller, TotalNumberOfBytes, TotalNumberOfFreeBytes; + GetDiskFreeSpaceEx(RootPath, &FreeBytesAvailableToCaller, &TotalNumberOfBytes, &TotalNumberOfFreeBytes); + if(DiskTestSize > TotalNumberOfFreeBytes.QuadPart / 1024 / 1024 ) + { + AfxMessageBox(((CDiskMarkDlg*)dlg)->m_MesDiskCapacityError); + ((CDiskMarkDlg*)dlg)->m_DiskBenchStatus = FALSE; + return FALSE; + } + + CString title; + title.Format(L"Preparing... Create Test File"); + ::PostMessage(((CDiskMarkDlg*)dlg)->GetSafeHwnd(), WM_UPDATE_MESSAGE, (WPARAM)& title, 0); + +// Preapare Test File + hFile = ::CreateFile(TestFilePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL|FILE_FLAG_NO_BUFFERING|FILE_FLAG_SEQUENTIAL_SCAN, NULL); + + if(hFile == INVALID_HANDLE_VALUE) + { + AfxMessageBox(((CDiskMarkDlg*)dlg)->m_MesDiskCreateFileError); + ((CDiskMarkDlg*)dlg)->m_DiskBenchStatus = FALSE; + return FALSE; + } + +// Set End Of File to prevent fragmentation of test file + LARGE_INTEGER nFileSize; + nFileSize.QuadPart = 1024 * 1024 * DiskTestSize; + + LARGE_INTEGER nStart; + nStart.QuadPart = 0; + + SetFilePointerEx(hFile, nFileSize, NULL, FILE_BEGIN); + SetEndOfFile(hFile); + SetFilePointerEx(hFile, nStart, NULL, FILE_BEGIN); + +// COMPRESSION_FORMAT_NONE + USHORT lpInBuffer = COMPRESSION_FORMAT_NONE; + DWORD lpBytesReturned = 0; + DeviceIoControl(hFile, FSCTL_SET_COMPRESSION, (LPVOID) &lpInBuffer, + sizeof(USHORT), NULL, 0, (LPDWORD)&lpBytesReturned, NULL); + +// Fill Test Data + char* buf = NULL; + int BufSize; + int Loop; + int i; + DWORD writesize; + BufSize = 1024 * 1024; + Loop = (int)DiskTestSize; + + buf = (char*) VirtualAlloc(NULL, BufSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + if (buf == NULL) + { + AfxMessageBox(L"Failed VirtualAlloc()."); + ((CDiskMarkDlg*) dlg)->m_DiskBenchStatus = FALSE; + return FALSE; + } + + if (((CDiskMarkDlg*) dlg)->m_TestData == TEST_DATA_ALL0X00) + { + for (i = 0; i < BufSize; i++) + { + buf[i] = 0; + } + } + else + { + // Compatible with DiskSpd + for (i = 0; i < BufSize; i++) + { + buf[i] = (char) (rand() % 256); + } + } + + for (i = 0; i < Loop; i++) + { + if (((CDiskMarkDlg*) dlg)->m_DiskBenchStatus) + { + result = WriteFile(hFile, buf, BufSize, &writesize, NULL); + } + else + { + CloseHandle(hFile); + VirtualFree(buf, 0, MEM_RELEASE); + ((CDiskMarkDlg*) dlg)->m_DiskBenchStatus = FALSE; + return FALSE; + } + } + VirtualFree(buf, 0, MEM_RELEASE); + CloseHandle(hFile); + + return TRUE; +} + +void CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) +{ + if(idEvent == TIMER_ID) + { + WaitFlag = FALSE; + KillTimer(hwnd, idEvent); + } +} + +UINT Exit(void* dlg) +{ + DeleteFile(TestFilePath); + RemoveDirectory(TestFileDir); + + static CString cstr; + cstr = L""; + + if(((CDiskMarkDlg*)dlg)->m_TestData == TEST_DATA_ALL0X00) + { + cstr = ALL_0X00_0FILL; + } + + ::PostMessage(((CDiskMarkDlg*)dlg)->GetSafeHwnd(), WM_UPDATE_MESSAGE, NULL, (LPARAM)&cstr); + ::PostMessage(((CDiskMarkDlg*)dlg)->GetSafeHwnd(), WM_EXIT_BENCHMARK, 0, 0); + + ((CDiskMarkDlg*)dlg)->m_DiskBenchStatus = FALSE; + ((CDiskMarkDlg*)dlg)->m_WinThread = NULL; + + return 0; +} + +void DiskSpd(void* dlg, DISK_SPD_CMD cmd) +{ + static CString cstr; + double *maxScore = NULL; + double *minLatency = NULL; + CString command; + CString title; + CString option; + CString bufOption; + + int duration = 5; + int index = 0; + int j = 0; + + if (!((CDiskMarkDlg*) dlg)->m_DiskBenchStatus) + { + return; + } + + if (((CDiskMarkDlg*) dlg)->m_TestData == TEST_DATA_ALL0X00) + { + bufOption += L" -Z"; + } + else + { + switch (cmd) + { + case TEST_WRITE_0: + case TEST_WRITE_1: + case TEST_WRITE_2: + case TEST_WRITE_3: + case TEST_WRITE_4: + case TEST_WRITE_5: + case TEST_WRITE_6: + case TEST_WRITE_7: + case TEST_WRITE_8: + index = cmd - TEST_WRITE_0; + cstr.Format(L" -Z%dK", BenchSize[index]); + bufOption += cstr; + break; + case TEST_MIX_0: + case TEST_MIX_1: + case TEST_MIX_2: + case TEST_MIX_3: + case TEST_MIX_4: + case TEST_MIX_5: + case TEST_MIX_6: + case TEST_MIX_7: + case TEST_MIX_8: + index = cmd - TEST_MIX_0; + cstr.Format(L" -Z%dK", BenchSize[index]); + bufOption += cstr; + break; + } + } + + switch (cmd) + { + case TEST_READ_0: + case TEST_READ_1: + case TEST_READ_2: + case TEST_READ_3: + case TEST_READ_4: + case TEST_READ_5: + case TEST_READ_6: + case TEST_READ_7: + case TEST_READ_8: + index = cmd - TEST_READ_0; + if (BenchType[index]) + { + title.Format(L"Random Read"); + option.Format(L"-b%dK -o%d -t%d -W0 -S -w0 -r", BenchSize[index], BenchQueues[index], BenchThreads[index]); + } + else + { + title.Format(L"Sequential Read"); + option.Format(L"-b%dK -o%d -t%d -W0 -S -w0", BenchSize[index], BenchQueues[index], BenchThreads[index]); + } + maxScore = &(((CDiskMarkDlg*) dlg)->m_ReadScore[index]); + minLatency = &(((CDiskMarkDlg*)dlg)->m_ReadLatency[index]); + break; + case TEST_WRITE_0: + case TEST_WRITE_1: + case TEST_WRITE_2: + case TEST_WRITE_3: + case TEST_WRITE_4: + case TEST_WRITE_5: + case TEST_WRITE_6: + case TEST_WRITE_7: + case TEST_WRITE_8: + index = cmd - TEST_WRITE_0; + if (BenchType[index]) + { + title.Format(L"Random Write"); + option.Format(L"-b%dK -o%d -t%d -W0 -S -w100 -r", BenchSize[index], BenchQueues[index], BenchThreads[index]); + } + else + { + title.Format(L"Sequential Write"); + option.Format(L"-b%dK -o%d -t%d -W0 -S -w100", BenchSize[index], BenchQueues[index], BenchThreads[index]); + } + option += bufOption; + maxScore = &(((CDiskMarkDlg*)dlg)->m_WriteScore[index]); + minLatency = &(((CDiskMarkDlg*)dlg)->m_WriteLatency[index]); + break; +#ifdef MIX_MODE + case TEST_MIX_0: + case TEST_MIX_1: + case TEST_MIX_2: + case TEST_MIX_3: + case TEST_MIX_4: + case TEST_MIX_5: + case TEST_MIX_6: + case TEST_MIX_7: + case TEST_MIX_8: + index = cmd - TEST_MIX_0; + if (BenchType[index]) + { + title.Format(L"Random Mix"); + option.Format(L"-b%dK -o%d -t%d -W0 -S -w%d -r", BenchSize[index], BenchQueues[index], BenchThreads[index], MixRatio); + } + else + { + title.Format(L"Sequential Mix"); + option.Format(L"-b%dK -o%d -t%d -W0 -S -w%d", BenchSize[index], BenchQueues[index], BenchThreads[index], MixRatio); + } + option += bufOption; + maxScore = &(((CDiskMarkDlg*)dlg)->m_MixScore[index]); + minLatency = &(((CDiskMarkDlg*)dlg)->m_MixLatency[index]); + break; +#endif + } + + option += L" -ag"; + if(IsWin8orLater() && BenchType[index] == 0 && BenchThreads[index] > 1) // Sequential + { + option += L" -si"; + } + + double score = 0.0; + double latency = 0.0; + + if (maxScore == NULL || minLatency == NULL) + { + return ; + } + *maxScore = 0.0; + *minLatency = -1.0; + + for (j = 0; j <= DiskTestCount; j++) + { + if (j == 0) + { + duration = ((CDiskMarkDlg*)dlg)->m_MeasureTime; + cstr.Format(L"Preparing... %s", title.GetString()); + } + else + { + duration = ((CDiskMarkDlg*)dlg)->m_MeasureTime; + cstr.Format(L"%s (%d/%d)", title.GetString(), j, DiskTestCount); + } + ::PostMessage(((CDiskMarkDlg*) dlg)->GetSafeHwnd(), WM_UPDATE_MESSAGE, (WPARAM) &cstr, 0); + + + command.Format(L"\"%s\" %s -d%d -A%d -L \"%s\"", (LPTSTR)DiskSpdExe.GetString(), (LPTSTR)option.GetString(), duration, GetCurrentProcessId(), (LPTSTR)TestFilePath.GetString()); + + score = ExecAndWait((TCHAR*) (command.GetString()), TRUE, &latency) / 10 / 1000.0; + + if (j > 0 && score > *maxScore) + { + *maxScore = score; + ::PostMessage(((CDiskMarkDlg*) dlg)->GetSafeHwnd(), WM_UPDATE_SCORE, 0, 0); + } + + if (j > 0 && score > 0.0 && (latency < *minLatency || *minLatency < 0)) + { + *minLatency = latency; + ::PostMessage(((CDiskMarkDlg*)dlg)->GetSafeHwnd(), WM_UPDATE_SCORE, 0, 0); + } + + if (!((CDiskMarkDlg*) dlg)->m_DiskBenchStatus) + { + return; + } + } + ::PostMessage(((CDiskMarkDlg*) dlg)->GetSafeHwnd(), WM_UPDATE_SCORE, 0, 0); +} diff --git a/CristalDiskMark/source/CrystalDiskMark/DiskBench.h b/CristalDiskMark/source/CrystalDiskMark/DiskBench.h new file mode 100644 index 0000000..583bbd0 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/DiskBench.h @@ -0,0 +1,88 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once + +#define TIMER_ID 5963 + +// Common Message +#define ALL_0X00_0FILL L"<0Fill>" + +// Benchmark +#define BENCHMARK_READ 1 +#define BENCHMARK_WRITE 2 +#define BENCHMARK_READ_WRITE 3 + +enum TEST_DATA_TYPE +{ + TEST_DATA_RANDOM = 0, + TEST_DATA_ALL0X00, + TEST_DATA_ALL0XFF, +}; + +enum AFFINITY_MODE +{ + AFFINITY_DISABLED = 0, + AFFINITY_ENABLED, +}; + +enum PROFILE +{ + PROFILE_DEFAULT = 0, + PROFILE_PEAK, + PROFILE_REAL, + PROFILE_DEMO, + PROFILE_DEFAULT_MIX, + PROFILE_PEAK_MIX, + PROFILE_REAL_MIX, +}; + +enum DISK_SPD_CMD +{ + TEST_CREATE_FILE = 0, + TEST_DELETE_FILE, + TEST_READ_0, + TEST_READ_1, + TEST_READ_2, + TEST_READ_3, + TEST_READ_4, + TEST_READ_5, + TEST_READ_6, + TEST_READ_7, + TEST_READ_8, + TEST_WRITE_0, + TEST_WRITE_1, + TEST_WRITE_2, + TEST_WRITE_3, + TEST_WRITE_4, + TEST_WRITE_5, + TEST_WRITE_6, + TEST_WRITE_7, + TEST_WRITE_8, + TEST_MIX_0, + TEST_MIX_1, + TEST_MIX_2, + TEST_MIX_3, + TEST_MIX_4, + TEST_MIX_5, + TEST_MIX_6, + TEST_MIX_7, + TEST_MIX_8, +}; + +UINT ExecDiskBenchAll(LPVOID dlg); +UINT ExecDiskBenchAllPeak(LPVOID dlg); +UINT ExecDiskBenchAllReal(LPVOID dlg); +UINT ExecDiskBenchAllDemo(LPVOID dlg); +UINT ExecDiskBench0(LPVOID dlg); +UINT ExecDiskBench1(LPVOID dlg); +UINT ExecDiskBench2(LPVOID dlg); +UINT ExecDiskBench3(LPVOID dlg); +UINT ExecDiskBench4(LPVOID dlg); +UINT ExecDiskBench5(LPVOID dlg); +UINT ExecDiskBench6(LPVOID dlg); +UINT ExecDiskBench7(LPVOID dlg); diff --git a/CristalDiskMark/source/CrystalDiskMark/DiskMark.cpp b/CristalDiskMark/source/CrystalDiskMark/DiskMark.cpp new file mode 100644 index 0000000..bc48171 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/DiskMark.cpp @@ -0,0 +1,117 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "DiskMark.h" +#include "DiskMarkDlg.h" + +#include +#pragma comment(lib, "winmm.lib") + +#include + +#ifdef _DEBUG +#define new DEBUG_NEW +#endif + +BEGIN_MESSAGE_MAP(CDiskMarkApp, CWinApp) + ON_COMMAND(ID_HELP, &CWinApp::OnHelp) +END_MESSAGE_MAP() + +CDiskMarkApp::CDiskMarkApp() +{ +} + +CDiskMarkApp theApp; + +//----------------------------------------------------------------------------- +// Prototypes +//----------------------------------------------------------------------------- +static BOOL IsFileExistEx(const TCHAR* path, const TCHAR* fileName); +static BOOL RunAsRestart(); + +BOOL CDiskMarkApp::InitInstance() +{ + // Remove current directory from DLL search order to prevent DLL hijacking + typedef BOOL(WINAPI* PFN_SetDllDirectory)(LPCTSTR); + PFN_SetDllDirectory pfnSetDllDirectory = (PFN_SetDllDirectory)GetProcAddress( + GetModuleHandle(_T("kernel32")), "SetDllDirectoryW"); + if (pfnSetDllDirectory) { pfnSetDllDirectory(_T("")); } + + INITCOMMONCONTROLSEX InitCtrls; + InitCtrls.dwSize = sizeof(InitCtrls); + InitCtrls.dwICC = ICC_WIN95_CLASSES; + InitCommonControlsEx(&InitCtrls); + + CWinApp::InitInstance(); + +#ifndef UWP + if (! IsUserAnAdmin()) + { + if (RunAsRestart()) + { + return FALSE; + } + } +#endif + + // Multimedia Timer Setting + TIMECAPS tc; + timeGetDevCaps(&tc,sizeof(TIMECAPS)); + timeBeginPeriod(tc.wPeriodMin); + + BOOL flagReExec = FALSE; + + CDiskMarkDlg dlg; + m_pMainWnd = &dlg; + + if (dlg.DoModal() == RE_EXEC) + { + flagReExec = TRUE; + } + + timeEndPeriod(tc.wPeriodMin); + + if(flagReExec) + { + TCHAR str[MAX_PATH]; + ::GetModuleFileName(NULL, str, MAX_PATH); + ShellExecute(NULL, NULL, str, NULL, NULL, SW_SHOWNORMAL); + } + + return FALSE; +} + +BOOL IsFileExistEx(const TCHAR* path, const TCHAR* fileName) +{ + if(! IsFileExist(path)) + { + CString cstr; + cstr.Format(L"Not Found \"%s\".", fileName); + AfxMessageBox(cstr); + return FALSE; + } + return TRUE; +} + +BOOL RunAsRestart() +{ + int count; + + TCHAR** cmd = ::CommandLineToArgvW(::GetCommandLine(), &count); + + if (count < 2 || _tcscmp(cmd[1], L"runas") != 0) + { + TCHAR path[MAX_PATH]; + ::GetModuleFileName(NULL, path, MAX_PATH); + if (::ShellExecute(NULL, L"runas", path, L"runas", NULL, SW_SHOWNORMAL) > (HINSTANCE)32) + { + return TRUE; + } + } + return FALSE; +} \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/DiskMark.h b/CristalDiskMark/source/CrystalDiskMark/DiskMark.h new file mode 100644 index 0000000..15a70b8 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/DiskMark.h @@ -0,0 +1,24 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once + +#include "resource.h" + +class CDiskMarkApp : public CWinApp +{ +public: + CDiskMarkApp(); + +public: + virtual BOOL InitInstance(); + + + DECLARE_MESSAGE_MAP() +}; + +extern CDiskMarkApp theApp; \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/DiskMark.rc b/CristalDiskMark/source/CrystalDiskMark/DiskMark.rc new file mode 100644 index 0000000..bc527d4 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/DiskMark.rc @@ -0,0 +1,477 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// { ({) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_JPN) +LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT +#pragma code_page(932) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_JPN)\r\n" + "LANGUAGE 17, 1\r\n" + "#pragma code_page(932)\r\n" + "#include ""res\\DiskMark.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DISKMARK_DIALOG DIALOGEX 0, 0, 267, 344 +STYLE DS_SETFONT | DS_MODALFRAME | WS_MINIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "CrystalDiskMark" +MENU IDR_MENU +FONT 8, "MS Shell Dlg", 400, 0, 0x0 +BEGIN + PUSHBUTTON "", IDC_HIDE, 0, 0, 0, 0 | WS_TABSTOP + PUSHBUTTON "All",IDC_BUTTON_ALL,7,32,62,37, BS_CENTER | BS_MULTILINE | BS_NOTIFY | WS_TABSTOP + COMBOBOX IDC_COMBO_COUNT,56,14,39,200, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_SIZE,103,14,45,200, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_DRIVE,160,14,99,200, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_UNIT, 0, 14, 39, 200, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP +#ifdef _MIX_MODE + COMBOBOX IDC_COMBO_MIX, 56, 14, 39, 200, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP +#endif + PUSHBUTTON "TEST0",IDC_BUTTON_TEST_0,7,70,62,37, BS_CENTER | BS_MULTILINE | BS_NOTIFY + PUSHBUTTON "TEST1",IDC_BUTTON_TEST_1,7,109,62,37, BS_CENTER | BS_MULTILINE | BS_NOTIFY + PUSHBUTTON "TEST2",IDC_BUTTON_TEST_2,7,149,62,37, BS_CENTER | BS_MULTILINE | BS_NOTIFY + PUSHBUTTON "TEST3",IDC_BUTTON_TEST_3,7,189,62,37, BS_CENTER | BS_MULTILINE | BS_NOTIFY + LTEXT "",IDC_TEST_READ_0,72,69,77,37, + LTEXT "",IDC_TEST_READ_1,71,108,77,37 + LTEXT "",IDC_TEST_READ_2,70,149,77,37 + LTEXT "",IDC_TEST_READ_3,71,189,77,37 + LTEXT "",IDC_TEST_WRITE_0,160,69,77,37 + LTEXT "",IDC_TEST_WRITE_1,160,109,77,37 + LTEXT "",IDC_TEST_WRITE_2,156,149,77,37 + LTEXT "",IDC_TEST_WRITE_3,156,190,77,37 + CONTROL "", IDC_COMMENT, "EDIT", WS_CHILD | ES_MULTILINE | ES_WANTRETURN | WS_TABSTOP, 7, 269, 252, 21, + LTEXT "Read [MB/s]",IDC_READ_UNIT,76,58,29,8 + LTEXT "Write [MB/s]",IDC_WRITE_UNIT,160,60,29,8 + LTEXT "Demo", IDC_DEMO_SETTING, 76, 58, 29, 8 + +#ifdef _MIX_MODE + LTEXT "", IDC_TEST_MIX_0, 160, 69, 77, 37, SS_NOTIFY + LTEXT "", IDC_TEST_MIX_1, 160, 109, 77, 37, SS_NOTIFY + LTEXT "", IDC_TEST_MIX_2, 156, 149, 77, 37, SS_NOTIFY + LTEXT "", IDC_TEST_MIX_3, 156, 190, 77, 37, SS_NOTIFY + LTEXT "Mix [MB/s]",IDC_MIX_UNIT,160,60,29,8 +#endif +END + +IDD_ABOUT DIALOGEX 0, 0, 477, 362 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "CrystalDiskMark" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + PUSHBUTTON "",IDC_LOGO,26,17,156,107 + PUSHBUTTON "",IDC_VERSION,240,18,193,8 + LTEXT "",IDC_EDITION,241,38,33,8 + LTEXT "",IDC_RELEASE,239,62,33,8 + LTEXT "",IDC_COPYRIGHT1,241,84,33,8 + LTEXT "",IDC_COPYRIGHT2, 241, 84, 33, 8 + LTEXT "",IDC_COPYRIGHT3, 241, 84, 33, 8 + PUSHBUTTON "",IDC_LICENSE,239,110,33,8 + PUSHBUTTON "",IDC_PROJECT_SITE_1,77,144,228,21 + PUSHBUTTON "",IDC_PROJECT_SITE_2,77,194,232,22 + PUSHBUTTON "",IDC_PROJECT_SITE_3,196,229,270,31 + PUSHBUTTON "",IDC_PROJECT_SITE_4,196,267,270,31 + PUSHBUTTON "",IDC_PROJECT_SITE_5,196,267,270,31 +END + +IDD_SETTINGS DIALOGEX 0, 0, 154, 76 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Queues&Threads" +FONT 8, "MS Shell Dlg", 400, 0, 0x0 +BEGIN + LTEXT "Default", IDC_LABEL_DEFAULT, 72, 69, 77, 37 + LTEXT "Peak", IDC_LABEL_PEAK, 72, 69, 77, 37 + LTEXT "Demo", IDC_LABEL_DEMO, 72, 69, 77, 37 + LTEXT "Measure Time", IDC_LABEL_MEASURE_TIME, 72, 69, 77, 37 + LTEXT "Interval Time", IDC_LABEL_INTERVAL_TIME, 72, 69, 77, 37 + LTEXT "Type",IDC_LABEL_TYPE, 72, 69, 77, 37 + LTEXT "Size",IDC_LABEL_SIZE,72,69,77,37 + LTEXT "Queues",IDC_LABEL_QUEUES,71,108,77,37 + LTEXT "Threads",IDC_LABEL_THREADS,71,108,77,37 + + COMBOBOX IDC_COMBO_BENCH_TYPE_0, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_SIZE_0, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_QUEUE_0, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_THREAD_0, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_TYPE_1, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_SIZE_1, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_QUEUE_1, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_THREAD_1, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_TYPE_2, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_SIZE_2, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_QUEUE_2, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_THREAD_2, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_TYPE_3, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_SIZE_3, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_QUEUE_3, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_THREAD_3, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_TYPE_4, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_SIZE_4, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_QUEUE_4, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_THREAD_4, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_TYPE_5, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_SIZE_5, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_QUEUE_5, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_THREAD_5, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + + COMBOBOX IDC_COMBO_BENCH_TYPE_8, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_SIZE_8, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_QUEUE_8, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_BENCH_THREAD_8, 1, 1, 1, 1, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_MEASURE_TIME, 56, 14, 39, 30, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_INTERVAL_TIME, 56, 14, 39, 30, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + + PUSHBUTTON "Default", IDC_SET_DEFAULT, 26, 17, 156, 107, BS_CENTER | BS_MULTILINE + PUSHBUTTON "NVMe SSD", IDC_SET_NVME_8, 26, 17, 156, 107, BS_CENTER | BS_MULTILINE + PUSHBUTTON "Flash Memory", IDC_SET_FLASH_MEMORY, 26, 17, 156, 107, BS_CENTER | BS_MULTILINE + PUSHBUTTON "OK", IDC_OK, 48, 68, 83, 14, BS_CENTER +END + +IDD_FONT DIALOGEX 0, 0, 180, 89 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Font Setting" +FONT 12, "MS Shell Dlg", 400, 0, 0x80 +BEGIN + LTEXT "Font Face", IDC_FONT_FACE, 1, 1, 1, 1 + LTEXT "Font Scale", IDC_FONT_SCALE, 1, 1, 1, 1 + LTEXT "Render Method", IDC_FONT_RENDER, 1, 1, 1, 1 + + COMBOBOX IDC_FONT_FACE_COMBO,7,6,166,20,CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_FONT_SCALE_COMBO, 7, 28, 166, 148, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_FONT_RENDER_COMBO, 7, 28, 166, 148, CBS_DROPDOWNLIST | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Default", IDC_SET_DEFAULT, 26, 17, 156, 107, BS_CENTER + PUSHBUTTON "OK",IDC_OK,48,68,83,14,BS_CENTER +END + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 9,0,3,0 + PRODUCTVERSION 9,0,3,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "041103a4" + BEGIN + VALUE "FileVersion", "9.0.3.0" + VALUE "ProductVersion", "9.0.3.0" + VALUE "OriginalFilename", "DiskMark.exe" + VALUE "InternalName", "DiskMark.exe" + VALUE "Comments", "https://crystalmark.info/" + VALUE "CompanyName", "Crystal Dew World" + +#ifdef SUISHO_AOI_SUPPORT + VALUE "FileDescription", "CrystalDiskMark Aoi Edition" + VALUE "ProductName", "CrystalDiskMark Aoi Edition" + VALUE "LegalCopyright", "(C) 2007-2026 hiyohiyo, (C) 2023-2026 nijihashi sola" +#elif MSI_MEI_SUPPORT + VALUE "FileDescription", "CrystalDiskMark MSI Mei Mihoshi Edition" + VALUE "ProductName", "CrystalDiskMark MSI Mei Mihoshi Edition" + VALUE "LegalCopyright", "(C) 2007-2026 hiyohiyo, (C) 2024-2026 Micro-Star INT'L CO., LTD." +#elif SUISHO_SHIZUKU_SUPPORT + VALUE "FileDescription", "CrystalDiskMark Shizuku Edition" + VALUE "ProductName", "CrystalDiskMark Shizuku Edition" + VALUE "LegalCopyright", "(C) 2007-2026 hiyohiyo, (C) 2012-2026 kirino kasumu" +#else + VALUE "FileDescription", "CrystalDiskMark" + VALUE "ProductName", "CrystalDiskMark" + VALUE "LegalCopyright", "(C) 2007-2026 hiyohiyo6" +#endif + + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x411, 932 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_DISKMARK_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 259 + TOPMARGIN, 7 + BOTTOMMARGIN, 338 + END + + IDD_ABOUT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 161 + TOPMARGIN, 7 + BOTTOMMARGIN, 95 + END + + IDD_SETTINGS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 147 + TOPMARGIN, 7 + BOTTOMMARGIN, 69 + END + + IDD_FONT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 173 + TOPMARGIN, 6 + BOTTOMMARGIN, 82 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDR_MENU MENU +BEGIN + POPUP "&File" + BEGIN + MENUITEM "Copy", ID_COPY + MENUITEM "Save (text)", ID_SAVE_TEXT + MENUITEM "Save (image)", ID_SAVE_IMAGE + MENUITEM "Exit", ID_EXIT + END + POPUP "&Settings" + BEGIN + POPUP "Test Mode" + BEGIN + MENUITEM "Default (Random)", ID_MODE_DEFAULT + MENUITEM "All 0x00 (0 Fill)", ID_MODE_ALL0X00 + END + MENUITEM SEPARATOR + MENUITEM "Default", ID_SETTING_DEFAULT + MENUITEM "NVMe SSD", ID_SETTING_NVME_8 + MENUITEM "Flash Memory", ID_SETTING_FLASH_MEMORY + MENUITEM SEPARATOR + MENUITEM "&Settings", ID_SETTINGS_QUEUESTHREADS + END + POPUP "&Profile" + BEGIN + MENUITEM "Default", ID_PROFILE_DEFAULT + MENUITEM "Peak Performance", ID_PROFILE_PEAK + MENUITEM "Real World Performance", ID_PROFILE_REAL + MENUITEM "Demonstration", ID_PROFILE_DEMO + +#ifdef _MIX_MODE + MENUITEM "Default w/MIX", ID_PROFILE_DEFAULT_MIX + MENUITEM "Peak Performance w/ MIX", ID_PROFILE_PEAK_MIX + MENUITEM "Real World Performance w/ MIX", ID_PROFILE_REAL_MIX +#endif + MENUITEM SEPARATOR + +#ifdef SUISHO_SHIZUKU_SUPPORT + MENUITEM "Read&&Wr&ite", ID_BENCHMARK_READ_WRITE + MENUITEM "Re&ad", ID_BENCHMARK_READ_ONLY + MENUITEM "&Write", ID_BENCHMARK_WRITE_ONLY +#else + MENUITEM "Read&&Wr&ite [+Mix]", ID_BENCHMARK_READ_WRITE + MENUITEM "Re&ad [+Mix]", ID_BENCHMARK_READ_ONLY + MENUITEM "&Write [+Mix]", ID_BENCHMARK_WRITE_ONLY +#endif + + END + POPUP "&Theme" + BEGIN + POPUP "Zoom" + BEGIN + MENUITEM "100%", ID_ZOOM_100 + MENUITEM "125%", ID_ZOOM_125 + MENUITEM "150%", ID_ZOOM_150 + MENUITEM "200%", ID_ZOOM_200 + MENUITEM "250%", ID_ZOOM_250 + MENUITEM "300%", ID_ZOOM_300 + MENUITEM "Auto", ID_ZOOM_AUTO + END + MENUITEM "Font Setting", ID_FONT_SETTING + MENUITEM SEPARATOR + END + POPUP "&Help" + BEGIN + MENUITEM "Help", ID_HELP + MENUITEM "Crystal Dew World [Web]", ID_CRYSTALDEWWORLD + MENUITEM SEPARATOR + MENUITEM "About CrystalDiskMark", ID_ABOUT + END + POPUP "&Language" + BEGIN + POPUP "A-N" + BEGIN + MENUITEM "DUMMY", ID_A_DUMMY + END + POPUP "O-Z" + BEGIN + MENUITEM "DUMMY", ID_O_DUMMY + END + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +#ifdef SUISHO_AOI_SUPPORT +IDR_MAINFRAME ICON "res\\DiskMarkA.ico" +IDI_TRAY_ICON ICON "res\\DiskMarkA16.ico" +#elif MSI_MEI_SUPPORT +IDR_MAINFRAME ICON "res\\DiskMarkM.ico" +IDI_TRAY_ICON ICON "res\\DiskMarkM16.ico" +#elif SUISHO_SHIZUKU_SUPPORT +IDR_MAINFRAME ICON "res\\DiskMarkS.ico" +IDI_TRAY_ICON ICON "res\\DiskMarkS16.ico" +#else +IDR_MAINFRAME ICON "res\\DiskMark.ico" +IDI_TRAY_ICON ICON "res\\DiskMark16.ico" +#endif + +///////////////////////////////////////////////////////////////////////////// +// +// Accelerator +// + +IDR_ACCELERATOR ACCELERATORS +BEGIN + "C", ID_COPY, VIRTKEY, CONTROL, SHIFT, NOINVERT + "T", ID_SAVE_TEXT, VIRTKEY, CONTROL, NOINVERT + "S", ID_SAVE_IMAGE, VIRTKEY, CONTROL, NOINVERT + VK_F4, ID_EXIT, VIRTKEY, ALT, NOINVERT + VK_F1, ID_HELP, VIRTKEY, NOINVERT + "Q", ID_SETTINGS_QUEUESTHREADS, VIRTKEY, CONTROL, NOINVERT + "F", ID_FONT_SETTING, VIRTKEY, CONTROL, NOINVERT + VK_ESCAPE, IDOK, VIRTKEY, NOINVERT + VK_F5, IDOK, VIRTKEY, NOINVERT +END + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_DISKMARK_DIALOG AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_SETTINGS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // { ({) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_JPN) +LANGUAGE 17, 1 +#pragma code_page(932) +#include "res\DiskMark.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/CristalDiskMark/source/CrystalDiskMark/DiskMark.sln b/CristalDiskMark/source/CrystalDiskMark/DiskMark.sln new file mode 100644 index 0000000..f4bb214 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/DiskMark.sln @@ -0,0 +1,114 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33502.453 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DiskMark", "DiskMark.vcxproj", "{CDF33C67-147E-4C50-BC76-99A6BCB214D9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release(UWP)|ARM = Release(UWP)|ARM + Release(UWP)|ARM64 = Release(UWP)|ARM64 + Release(UWP)|Win32 = Release(UWP)|Win32 + Release(UWP)|x64 = Release(UWP)|x64 + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + ReleaseAoi(UWP)|ARM = ReleaseAoi(UWP)|ARM + ReleaseAoi(UWP)|ARM64 = ReleaseAoi(UWP)|ARM64 + ReleaseAoi(UWP)|Win32 = ReleaseAoi(UWP)|Win32 + ReleaseAoi(UWP)|x64 = ReleaseAoi(UWP)|x64 + ReleaseAoi|ARM = ReleaseAoi|ARM + ReleaseAoi|ARM64 = ReleaseAoi|ARM64 + ReleaseAoi|Win32 = ReleaseAoi|Win32 + ReleaseAoi|x64 = ReleaseAoi|x64 + ReleaseMSIMei|ARM = ReleaseMSIMei|ARM + ReleaseMSIMei|ARM64 = ReleaseMSIMei|ARM64 + ReleaseMSIMei|Win32 = ReleaseMSIMei|Win32 + ReleaseMSIMei|x64 = ReleaseMSIMei|x64 + ReleaseShizuku(UWP)|ARM = ReleaseShizuku(UWP)|ARM + ReleaseShizuku(UWP)|ARM64 = ReleaseShizuku(UWP)|ARM64 + ReleaseShizuku(UWP)|Win32 = ReleaseShizuku(UWP)|Win32 + ReleaseShizuku(UWP)|x64 = ReleaseShizuku(UWP)|x64 + ReleaseShizuku|ARM = ReleaseShizuku|ARM + ReleaseShizuku|ARM64 = ReleaseShizuku|ARM64 + ReleaseShizuku|Win32 = ReleaseShizuku|Win32 + ReleaseShizuku|x64 = ReleaseShizuku|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Debug|ARM.ActiveCfg = Debug|ARM + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Debug|ARM.Build.0 = Debug|ARM + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Debug|ARM64.Build.0 = Debug|ARM64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Debug|Win32.ActiveCfg = Debug|Win32 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Debug|Win32.Build.0 = Debug|Win32 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Debug|x64.ActiveCfg = Debug|x64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Debug|x64.Build.0 = Debug|x64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Release(UWP)|ARM.ActiveCfg = Release(UWP)|ARM + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Release(UWP)|ARM.Build.0 = Release(UWP)|ARM + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Release(UWP)|ARM64.ActiveCfg = Release(UWP)|ARM64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Release(UWP)|ARM64.Build.0 = Release(UWP)|ARM64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Release(UWP)|Win32.ActiveCfg = Release(UWP)|Win32 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Release(UWP)|Win32.Build.0 = Release(UWP)|Win32 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Release(UWP)|x64.ActiveCfg = Release(UWP)|x64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Release(UWP)|x64.Build.0 = Release(UWP)|x64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Release|ARM.ActiveCfg = Release|ARM + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Release|ARM.Build.0 = Release|ARM + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Release|ARM64.ActiveCfg = Release|ARM64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Release|ARM64.Build.0 = Release|ARM64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Release|Win32.ActiveCfg = Release|Win32 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Release|Win32.Build.0 = Release|Win32 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Release|x64.ActiveCfg = Release|x64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.Release|x64.Build.0 = Release|x64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseAoi(UWP)|ARM.ActiveCfg = ReleaseAoi(UWP)|ARM + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseAoi(UWP)|ARM.Build.0 = ReleaseAoi(UWP)|ARM + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseAoi(UWP)|ARM64.ActiveCfg = ReleaseAoi(UWP)|ARM64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseAoi(UWP)|ARM64.Build.0 = ReleaseAoi(UWP)|ARM64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseAoi(UWP)|Win32.ActiveCfg = ReleaseAoi(UWP)|Win32 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseAoi(UWP)|Win32.Build.0 = ReleaseAoi(UWP)|Win32 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseAoi(UWP)|x64.ActiveCfg = ReleaseAoi(UWP)|x64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseAoi(UWP)|x64.Build.0 = ReleaseAoi(UWP)|x64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseAoi|ARM.ActiveCfg = ReleaseAoi|ARM + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseAoi|ARM.Build.0 = ReleaseAoi|ARM + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseAoi|ARM64.ActiveCfg = ReleaseAoi|ARM64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseAoi|ARM64.Build.0 = ReleaseAoi|ARM64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseAoi|Win32.ActiveCfg = ReleaseAoi|Win32 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseAoi|Win32.Build.0 = ReleaseAoi|Win32 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseAoi|x64.ActiveCfg = ReleaseAoi|x64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseAoi|x64.Build.0 = ReleaseAoi|x64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseMSIMei|ARM.ActiveCfg = ReleaseMSIMei|ARM + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseMSIMei|ARM.Build.0 = ReleaseMSIMei|ARM + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseMSIMei|ARM64.ActiveCfg = ReleaseMSIMei|ARM64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseMSIMei|ARM64.Build.0 = ReleaseMSIMei|ARM64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseMSIMei|Win32.ActiveCfg = ReleaseMSIMei|Win32 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseMSIMei|Win32.Build.0 = ReleaseMSIMei|Win32 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseMSIMei|x64.ActiveCfg = ReleaseMSIMei|x64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseMSIMei|x64.Build.0 = ReleaseMSIMei|x64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseShizuku(UWP)|ARM.ActiveCfg = ReleaseShizuku(UWP)|ARM + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseShizuku(UWP)|ARM.Build.0 = ReleaseShizuku(UWP)|ARM + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseShizuku(UWP)|ARM64.ActiveCfg = ReleaseShizuku(UWP)|ARM64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseShizuku(UWP)|ARM64.Build.0 = ReleaseShizuku(UWP)|ARM64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseShizuku(UWP)|Win32.ActiveCfg = ReleaseShizuku(UWP)|Win32 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseShizuku(UWP)|Win32.Build.0 = ReleaseShizuku(UWP)|Win32 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseShizuku(UWP)|x64.ActiveCfg = ReleaseShizuku(UWP)|x64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseShizuku(UWP)|x64.Build.0 = ReleaseShizuku(UWP)|x64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseShizuku|ARM.ActiveCfg = ReleaseShizuku|ARM + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseShizuku|ARM.Build.0 = ReleaseShizuku|ARM + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseShizuku|ARM64.ActiveCfg = ReleaseShizuku|ARM64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseShizuku|ARM64.Build.0 = ReleaseShizuku|ARM64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseShizuku|Win32.ActiveCfg = ReleaseShizuku|Win32 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseShizuku|Win32.Build.0 = ReleaseShizuku|Win32 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseShizuku|x64.ActiveCfg = ReleaseShizuku|x64 + {CDF33C67-147E-4C50-BC76-99A6BCB214D9}.ReleaseShizuku|x64.Build.0 = ReleaseShizuku|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {81DC78FD-9979-489D-930A-DA0CD60B9148} + EndGlobalSection +EndGlobal diff --git a/CristalDiskMark/source/CrystalDiskMark/DiskMark.vcxproj b/CristalDiskMark/source/CrystalDiskMark/DiskMark.vcxproj new file mode 100644 index 0000000..b86e789 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/DiskMark.vcxproj @@ -0,0 +1,3345 @@ + + + + + Debug + ARM + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release(UWP) + ARM + + + Release(UWP) + ARM64 + + + Release(UWP) + Win32 + + + Release(UWP) + x64 + + + ReleaseAoi(UWP) + ARM + + + ReleaseAoi(UWP) + ARM64 + + + ReleaseAoi(UWP) + Win32 + + + ReleaseAoi(UWP) + x64 + + + ReleaseMSIMei + ARM + + + ReleaseMSIMei + ARM64 + + + ReleaseMSIMei + Win32 + + + ReleaseMSIMei + x64 + + + ReleaseShizuku(UWP) + ARM + + + ReleaseShizuku(UWP) + ARM64 + + + ReleaseShizuku(UWP) + Win32 + + + ReleaseShizuku(UWP) + x64 + + + ReleaseShizuku + ARM + + + ReleaseShizuku + ARM64 + + + ReleaseShizuku + Win32 + + + ReleaseShizuku + x64 + + + ReleaseAoi + ARM + + + ReleaseAoi + ARM64 + + + ReleaseAoi + Win32 + + + ReleaseAoi + x64 + + + Release + ARM + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {CDF33C67-147E-4C50-BC76-99A6BCB214D9} + DiskMark + MFCProj + 10.0.22621.0 + + + + Application + Static + Unicode + true + v142 + + + Application + Static + Unicode + true + v142 + + + Application + Static + Unicode + true + v142 + + + Application + Static + Unicode + true + v142 + + + Application + Static + Unicode + true + v142 + + + Application + Static + Unicode + true + v142 + + + Application + Static + Unicode + true + v142 + + + Application + Static + Unicode + v142 + + + Application + Static + Unicode + true + v142 + + + Application + Static + Unicode + true + v142 + + + Application + Static + Unicode + true + v142 + + + Application + Static + Unicode + true + v143 + true + + + Application + Static + Unicode + true + v143 + true + + + Application + Static + Unicode + true + v143 + true + + + Application + Static + Unicode + true + v143 + true + + + Application + Static + Unicode + true + v143 + true + + + Application + Static + Unicode + true + v143 + true + + + Application + Static + Unicode + true + v142 + + + Application + Static + Unicode + true + v142 + + + Application + Static + Unicode + true + v143 + true + + + Application + Static + Unicode + true + v143 + true + + + Application + Static + Unicode + true + v143 + true + + + Application + Static + Unicode + true + v143 + true + + + Application + Static + Unicode + true + v142 + + + Application + Static + Unicode + true + v143 + true + + + Application + Static + Unicode + true + v143 + true + + + Application + Static + Unicode + true + v142 + + + Application + Static + Unicode + true + v143 + true + + + Application + Static + Unicode + true + v143 + true + + + Application + Static + Unicode + v142 + + + Application + Static + Unicode + v143 + true + + + Application + Static + Unicode + v143 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + ..\Hotori\ + Build\$(Platform)\$(Configuration)\ + true + ..\Hotori\ + Build\$(Platform)\$(Configuration)\ + true + true + true + ..\Hotori\ + ..\Hotori\ + ..\Hotori\ + ..\Hotori\ + ..\Hotori\ + ..\Hotori\ + ..\Hotori\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + false + false + false + false + false + false + false + true + true + true + true + true + true + true + ..\Hotori\ + ..\Hotori\ + ..\Hotori\ + ..\Hotori\ + ..\Hotori\ + ..\Hotori\ + ..\Hotori\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + Build\$(Platform)\$(Configuration)\ + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + + + $(ProjectName)64S + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)64A + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)64M + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)A64S + ..\Hotori\ + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)A64A + ..\Hotori\ + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)A64M + ..\Hotori\ + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)A32S + ..\Hotori\ + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)A32A + ..\Hotori\ + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)A32M + ..\Hotori\ + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)64S_UWP + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)64A_UWP + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)A64S_UWP + ..\Hotori\ + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)A64A_UWP + ..\Hotori\ + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)A32S_UWP + ..\Hotori\ + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)A32A_UWP + ..\Hotori\ + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)64 + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)A64 + ..\Hotori\ + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)A32 + ..\Hotori\ + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)64_UWP + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)A64_UWP + ..\Hotori\ + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)A32_UWP + ..\Hotori\ + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)32S + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)32A + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)32M + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)32S_UWP + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)32A_UWP + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)32 + .\Library;.\Priscilla;$(IncludePath) + + + $(ProjectName)32_UWP + .\Library;.\Priscilla;$(IncludePath) + + + Build\$(Platform)\$(Configuration)\ + .\Library;.\Priscilla;$(IncludePath) + + + Build\$(Platform)\$(Configuration)\ + .\Library;.\Priscilla;$(IncludePath) + + + .\Library;.\Priscilla;$(IncludePath) + + + .\Library;.\Priscilla;$(IncludePath) + + + + _DEBUG;%(PreprocessorDefinitions) + false + false + + + Disabled + MIX_MODE;WIN32;_WINDOWS;_DEBUG;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + Use + Level3 + EditAndContinue + /FS %(AdditionalOptions) + ..\stdafx.h + true + + + _MIX_MODE;_DEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + true + Windows + true + + + MachineX86 + ..\Hotori\DiskMark.exe + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + _DEBUG;%(PreprocessorDefinitions) + false + X64 + + + Disabled + MIX_MODE;WIN32;_WINDOWS;_DEBUG;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + Use + Level3 + ProgramDatabase + true + ..\stdafx.h + + + _MIX_MODE;_DEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + true + Windows + true + + + MachineX64 + /DEPENDENTLOADFLAG:0x800 /SUBSYSTEM:WINDOWS,5.02 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + _DEBUG;%(PreprocessorDefinitions) + false + + + Disabled + WIN32;_WINDOWS;_DEBUG;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + Use + Level3 + ProgramDatabase + ..\stdafx.h + true + + + _DEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + true + Windows + true + + + /DEPENDENTLOADFLAG:0x800 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + _DEBUG;%(PreprocessorDefinitions) + false + + + Disabled + WIN32;_WINDOWS;_DEBUG;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + Use + Level3 + ProgramDatabase + ..\stdafx.h + true + + + _DEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + true + Windows + true + + + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + false + + + MaxSpeed + Speed + SUISHO_SHIZUKU_SUPPORT;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + NoExtensions + true + ..\stdafx.h + true + + + SUISHO_SHIZUKU_SUPPORT;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + ..\Hotori\$(ProjectName)32S.exe + false + Windows + true + true + true + + + MachineX86 + /SUBSYSTEM:WINDOWS,5.01 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + false + + + MaxSpeed + Speed + SUISHO_AOI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + NoExtensions + true + ..\stdafx.h + true + + + SUISHO_AOI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + ..\Hotori\$(ProjectName)32A.exe + false + Windows + true + true + true + + + MachineX86 + /SUBSYSTEM:WINDOWS,5.01 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + false + + + MaxSpeed + Speed + MSI_MEI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + NoExtensions + true + ..\stdafx.h + true + + + MSI_MEI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + ..\Hotori\$(ProjectName)32M.exe + false + Windows + true + true + true + + + MachineX86 + /SUBSYSTEM:WINDOWS,5.01 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + false + + + MaxSpeed + Speed + UWP;SUISHO_SHIZUKU_SUPPORT;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + NoExtensions + true + ..\stdafx.h + true + + + SUISHO_SHIZUKU_SUPPORT;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + ..\Hotori\$(ProjectName)32S_UWP.exe + false + Windows + true + true + true + + + MachineX86 + /SUBSYSTEM:WINDOWS,5.01 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + false + + + MaxSpeed + Speed + UWP;SUISHO_AOI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + NoExtensions + true + ..\stdafx.h + true + + + SUISHO_AOI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + ..\Hotori\$(ProjectName)32A_UWP.exe + false + Windows + true + true + true + + + MachineX86 + /SUBSYSTEM:WINDOWS,5.01 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + false + + + MaxSpeed + Speed + MIX_MODE;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + NoExtensions + true + ..\stdafx.h + true + + + _MIX_MODE;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + ..\Hotori\$(ProjectName)32.exe + false + Windows + true + true + true + + + MachineX86 + /SUBSYSTEM:WINDOWS,5.01 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + false + + + MaxSpeed + Speed + MIX_MODE;UWP;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + NoExtensions + true + ..\stdafx.h + true + + + _MIX_MODE;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + ..\Hotori\$(ProjectName)32_UWP.exe + false + Windows + true + true + true + + + MachineX86 + /SUBSYSTEM:WINDOWS,5.01 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + X64 + + + Speed + SUISHO_SHIZUKU_SUPPORT;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + + + true + ..\stdafx.h + + + SUISHO_SHIZUKU_SUPPORT;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + MachineX64 + ..\Hotori\$(ProjectName)64S.exe + /DEPENDENTLOADFLAG:0x800 /SUBSYSTEM:WINDOWS,5.02 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + X64 + + + Speed + SUISHO_AOI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + + + true + ..\stdafx.h + + + SUISHO_AOI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + MachineX64 + ..\Hotori\$(ProjectName)64A.exe + /DEPENDENTLOADFLAG:0x800 /SUBSYSTEM:WINDOWS,5.02 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + X64 + + + Speed + MSI_MEI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + + + true + ..\stdafx.h + + + MSI_MEI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + MachineX64 + ..\Hotori\$(ProjectName)64M.exe + /DEPENDENTLOADFLAG:0x800 /SUBSYSTEM:WINDOWS,5.02 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + + + Speed + SUISHO_SHIZUKU_SUPPORT;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + true + ..\stdafx.h + true + + + SUISHO_SHIZUKU_SUPPORT;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + ..\Hotori\$(ProjectName)A64S.exe + /DEPENDENTLOADFLAG:0x800 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + + + Speed + SUISHO_AOI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + true + ..\stdafx.h + true + + + SUISHO_AOI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + ..\Hotori\$(ProjectName)A64A.exe + /DEPENDENTLOADFLAG:0x800 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + + + Speed + MSI_MEI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + true + ..\stdafx.h + true + + + MSI_MEI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + ..\Hotori\$(ProjectName)A64M.exe + /DEPENDENTLOADFLAG:0x800 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + + + Speed + SUISHO_SHIZUKU_SUPPORT;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + true + ..\stdafx.h + true + + + SUISHO_SHIZUKU_SUPPORT;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + ..\Hotori\$(ProjectName)A32S.exe + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + + + Speed + SUISHO_AOI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + true + ..\stdafx.h + true + + + SUISHO_AOI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + ..\Hotori\$(ProjectName)A32A.exe + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + + + Speed + MSI_MEI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + true + ..\stdafx.h + true + + + MSI_MEI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + ..\Hotori\$(ProjectName)A32M.exe + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + X64 + + + Speed + UWP;SUISHO_SHIZUKU_SUPPORT;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + true + ..\stdafx.h + + + SUISHO_SHIZUKU_SUPPORT;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + MachineX64 + ..\Hotori\$(ProjectName)64S_UWP.exe + /DEPENDENTLOADFLAG:0x800 /SUBSYSTEM:WINDOWS,5.02 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + X64 + + + Speed + UWP;SUISHO_AOI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + true + ..\stdafx.h + + + SUISHO_AOI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + MachineX64 + ..\Hotori\$(ProjectName)64A_UWP.exe + /DEPENDENTLOADFLAG:0x800 /SUBSYSTEM:WINDOWS,5.02 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + + + Speed + UWP;SUISHO_SHIZUKU_SUPPORT;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + true + ..\stdafx.h + true + + + SUISHO_SHIZUKU_SUPPORT;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + ..\Hotori\$(ProjectName)A64S_UWP.exe + /DEPENDENTLOADFLAG:0x800 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + + + Speed + UWP;SUISHO_AOI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + true + ..\stdafx.h + true + + + SUISHO_AOI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + ..\Hotori\$(ProjectName)A64A_UWP.exe + /DEPENDENTLOADFLAG:0x800 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + + + Speed + UWP;SUISHO_SHIZUKU_SUPPORT;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + true + ..\stdafx.h + true + + + SUISHO_SHIZUKU_SUPPORT;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + ..\Hotori\$(ProjectName)A32S_UWP.exe + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + + + Speed + UWP;SUISHO_AOI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + true + ..\stdafx.h + true + + + SUISHO_AOI_SUPPORT;SUISHO_SHIZUKU_SUPPORT;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + ..\Hotori\$(ProjectName)A32A_UWP.exe + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + X64 + + + Speed + MIX_MODE;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + true + + + true + ..\stdafx.h + + + _MIX_MODE;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + MachineX64 + ..\Hotori\$(ProjectName)64.exe + /DEPENDENTLOADFLAG:0x800 /SUBSYSTEM:WINDOWS,5.02 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + + + Speed + MIX_MODE;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + true + ..\stdafx.h + true + + + _MIX_MODE;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + ..\Hotori\$(ProjectName)A64.exe + /DEPENDENTLOADFLAG:0x800 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + + + Speed + MIX_MODE;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + true + ..\stdafx.h + true + + + _MIX_MODE;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + ..\Hotori\$(ProjectName)A32.exe + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + X64 + + + Speed + MIX_MODE;UWP;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + true + ..\stdafx.h + + + _MIX_MODE;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + MachineX64 + ..\Hotori\$(ProjectName)64_UWP.exe + /DEPENDENTLOADFLAG:0x800 /SUBSYSTEM:WINDOWS,5.02 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + + + Speed + MIX_MODE;UWP;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + true + ..\stdafx.h + true + + + _MIX_MODE;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + ..\Hotori\$(ProjectName)A64_UWP.exe + /DEPENDENTLOADFLAG:0x800 %(AdditionalOptions) + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + NDEBUG;%(PreprocessorDefinitions) + false + + + Speed + MIX_MODE;UWP;WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + false + MultiThreaded + Use + Level3 + ProgramDatabase + MaxSpeed + true + ..\stdafx.h + true + + + _MIX_MODE;NDEBUG;%(PreprocessorDefinitions) + 0x0411 + $(IntDir);%(AdditionalIncludeDirectories) + + + false + Windows + true + true + true + + + ..\Hotori\$(ProjectName)A32_UWP.exe + + + %(AdditionalManifestFiles) + PerMonitorHighDPIAware + + + + + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + Sync + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + NotUsing + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + NotUsing + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + NotUsing + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + NotUsing + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + + + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + + + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + NotUsing + stdafx.h + + + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + stdafx.h + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MIX_MODE;_DEBUG;%(PreprocessorDefinitions) + SUISHO_SHIZUKU_SUPPORT;MIX_MODE;_DEBUG;%(PreprocessorDefinitions) + MIX_MODE;NDEBUG;%(PreprocessorDefinitions) + MIX_MODE;NDEBUG;%(PreprocessorDefinitions) + MIX_MODE;NDEBUG;%(PreprocessorDefinitions) + MIX_MODE;NDEBUG;%(PreprocessorDefinitions) + MIX_MODE;NDEBUG;%(PreprocessorDefinitions) + MIX_MODE;NDEBUG;%(PreprocessorDefinitions) + MIX_MODE;NDEBUG;%(PreprocessorDefinitions) + MIX_MODE;NDEBUG;%(PreprocessorDefinitions) + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/DiskMark.vcxproj.filters b/CristalDiskMark/source/CrystalDiskMark/DiskMark.vcxproj.filters new file mode 100644 index 0000000..a10f1e7 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/DiskMark.vcxproj.filters @@ -0,0 +1,171 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + {342a84d1-5b9a-4a10-b0ca-cc618409b820} + + + {1e1cebf3-7ec5-4e7b-b130-da0db639e588} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Priscilla + + + Priscilla + + + Priscilla + + + Priscilla + + + Source Files + + + Priscilla + + + Priscilla + + + Priscilla + + + Priscilla + + + Priscilla + + + Priscilla + + + Library + + + Priscilla + + + Priscilla + + + Priscilla + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Priscilla + + + Priscilla + + + Priscilla + + + Priscilla + + + Priscilla + + + Priscilla + + + Header Files + + + Priscilla + + + Priscilla + + + Priscilla + + + Priscilla + + + Priscilla + + + Library + + + Library + + + Priscilla + + + Priscilla + + + + + Resource Files + + + Resource Files + + + Resource Files + + + + + Resource Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/DiskMarkDlg.cpp b/CristalDiskMark/source/CrystalDiskMark/DiskMarkDlg.cpp new file mode 100644 index 0000000..46aba55 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/DiskMarkDlg.cpp @@ -0,0 +1,3793 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "DiskMark.h" +#include "DiskMarkDlg.h" +#include "DiskBench.h" +#include "AboutDlg.h" + +#include + +#ifdef SUISHO_SHIZUKU_SUPPORT +#define SIZE_X 1000 +#define SIZE_Y 500 +#define SIZE_MIN_Y 500 +#define OFFSET_X 200 +#define MAX_METER_LENGTH 320 +#else +#define SIZE_X 480 +#define SIZE_X_MIX 680 +#define SIZE_Y 300 +#define SIZE_MIN_Y 300 +#define OFFSET_X 0 +#define MAX_METER_LENGTH 192 +#endif + +#ifdef _DEBUG +#define new DEBUG_NEW +#endif + +extern PROCESS_INFORMATION pi; + +CDiskMarkDlg::CDiskMarkDlg(CWnd* pParent /*=NULL*/) + : CMainDialogFx(CDiskMarkDlg::IDD, pParent) +{ + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); + m_hIconMini = AfxGetApp()->LoadIcon(IDI_TRAY_ICON); + + m_AboutDlg = NULL; + m_SettingsDlg = NULL; + +#ifdef SUISHO_AOI_SUPPORT + m_DefaultTheme = L"Aoi"; + m_RecommendTheme = L"AoiLightAnimalEars~TenmuShinryuusai"; + m_ThemeKeyName = L"ThemeAoi"; + + m_MarginButtonTop = 16; + m_MarginButtonLeft = 0; + m_MarginButtonBottom = 16; + m_MarginButtonRight = 0; + m_MarginMeterTop = 0; + m_MarginMeterLeft = 0; + m_MarginMeterBottom = 0; + m_MarginMeterRight = 16; + m_MarginCommentTop = 0; + m_MarginCommentLeft = 16; + m_MarginCommentBottom = 0; + m_MarginCommentRight = 16; + m_MarginDemoTop = 24; + m_MarginDemoLeft = 24; + m_MarginDemoBottom = 24; + m_MarginDemoRight = 24; +#elif MSI_MEI_SUPPORT + m_DefaultTheme = L"MSIMei"; + m_RecommendTheme = L"MSIMei"; + m_ThemeKeyName = L"ThemeMSIMei"; + + m_MarginButtonTop = 8; + m_MarginButtonLeft = 0; + m_MarginButtonBottom = 8; + m_MarginButtonRight = 0; + m_MarginMeterTop = 0; + m_MarginMeterLeft = 0; + m_MarginMeterBottom = 0; + m_MarginMeterRight = 16; + m_MarginCommentTop = 0; + m_MarginCommentLeft = 16; + m_MarginCommentBottom = 0; + m_MarginCommentRight = 64; + m_MarginDemoTop = 24; + m_MarginDemoLeft = 24; + m_MarginDemoBottom = 24; + m_MarginDemoRight = 24; +#elif SUISHO_SHIZUKU_SUPPORT + m_DefaultTheme = L"Shizuku"; + m_RecommendTheme = L"ShizukuLightAnimalEars~TenmuShinryuusai"; + m_ThemeKeyName = L"ThemeShizuku"; + + m_MarginButtonTop = 8; + m_MarginButtonLeft = 0; + m_MarginButtonBottom = 8; + m_MarginButtonRight = 0; + m_MarginMeterTop = 0; + m_MarginMeterLeft = 0; + m_MarginMeterBottom = 0; + m_MarginMeterRight = 16; + m_MarginCommentTop = 0; + m_MarginCommentLeft = 16; + m_MarginCommentBottom = 0; + m_MarginCommentRight = 64; + m_MarginDemoTop = 24; + m_MarginDemoLeft = 24; + m_MarginDemoBottom = 24; + m_MarginDemoRight = 24; +#else + m_DefaultTheme = L"Default"; + m_ThemeKeyName = L"Theme"; + + m_MarginButtonTop = 4; + m_MarginButtonLeft = 0; + m_MarginButtonBottom = 4; + m_MarginButtonRight = 0; + m_MarginMeterTop = 0; + m_MarginMeterLeft = 0; + m_MarginMeterBottom = 0; + m_MarginMeterRight = 4; + m_MarginCommentTop = 0; + m_MarginCommentLeft = 4; + m_MarginCommentBottom = 0; + m_MarginCommentRight = 4; + m_MarginDemoTop = 8; + m_MarginDemoLeft = 8; + m_MarginDemoBottom = 8; + m_MarginDemoRight = 8; +#endif + + m_BackgroundName = L"Background"; + m_RandomThemeLabel = L"Random"; + m_RandomThemeName = L""; + + m_AdminMode = IsUserAnAdmin(); +} + + +void CDiskMarkDlg::UpdateThemeInfo() +{ + CMainDialogFx::UpdateThemeInfo(); + + CString theme = m_ThemeDir + m_CurrentTheme + L"\\theme.ini"; + +#ifdef SUISHO_AOI_SUPPORT + m_MarginButtonTop = GetPrivateProfileInt(L"Margin", L"ButtonTop", 16, theme); + m_MarginButtonLeft = GetPrivateProfileInt(L"Margin", L"ButtonLeft", 0, theme); + m_MarginButtonBottom = GetPrivateProfileInt(L"Margin", L"ButtonBottom", 16, theme); + m_MarginButtonRight = GetPrivateProfileInt(L"Margin", L"ButtonRight", 0, theme); + m_MarginMeterTop = GetPrivateProfileInt(L"Margin", L"MeterTop", 0, theme); + m_MarginMeterLeft = GetPrivateProfileInt(L"Margin", L"MeterLeft", 0, theme); + m_MarginMeterBottom = GetPrivateProfileInt(L"Margin", L"MeterBottom", 0, theme); + m_MarginMeterRight = GetPrivateProfileInt(L"Margin", L"MeterRight", 16, theme); + m_MarginCommentTop = GetPrivateProfileInt(L"Margin", L"CommentTop", 0, theme); + m_MarginCommentLeft = GetPrivateProfileInt(L"Margin", L"CommentLeft", 4, theme); + m_MarginCommentBottom = GetPrivateProfileInt(L"Margin", L"CommentBottom", 0, theme); + m_MarginCommentRight = GetPrivateProfileInt(L"Margin", L"CommentRight", 4, theme); + m_MarginDemoTop = GetPrivateProfileInt(L"Margin", L"DemoTop", 24, theme); + m_MarginDemoLeft = GetPrivateProfileInt(L"Margin", L"DemoLeft", 24, theme); + m_MarginDemoBottom = GetPrivateProfileInt(L"Margin", L"DemoBottom", 24, theme); + m_MarginDemoRight = GetPrivateProfileInt(L"Margin", L"DemoRight", 24, theme); + +#elif MSI_MEI_SUPPORT + m_MarginButtonTop = GetPrivateProfileInt(L"Margin", L"ButtonTop", 8, theme); + m_MarginButtonLeft = GetPrivateProfileInt(L"Margin", L"ButtonLeft", 0, theme); + m_MarginButtonBottom = GetPrivateProfileInt(L"Margin", L"ButtonBottom", 8, theme); + m_MarginButtonRight = GetPrivateProfileInt(L"Margin", L"ButtonRight", 0, theme); + m_MarginMeterTop = GetPrivateProfileInt(L"Margin", L"MeterTop", 0, theme); + m_MarginMeterLeft = GetPrivateProfileInt(L"Margin", L"MeterLeft", 0, theme); + m_MarginMeterBottom = GetPrivateProfileInt(L"Margin", L"MeterBottom", 0, theme); + m_MarginMeterRight = GetPrivateProfileInt(L"Margin", L"MeterRight", 16, theme); + m_MarginCommentTop = GetPrivateProfileInt(L"Margin", L"CommentTop", 0, theme); + m_MarginCommentLeft = GetPrivateProfileInt(L"Margin", L"CommentLeft", 16, theme); + m_MarginCommentBottom = GetPrivateProfileInt(L"Margin", L"CommentBottom", 0, theme); + m_MarginCommentRight = GetPrivateProfileInt(L"Margin", L"CommentRight", 16, theme); + m_MarginDemoTop = GetPrivateProfileInt(L"Margin", L"DemoTop", 24, theme); + m_MarginDemoLeft = GetPrivateProfileInt(L"Margin", L"DemoLeft", 24, theme); + m_MarginDemoBottom = GetPrivateProfileInt(L"Margin", L"DemoBottom", 24, theme); + m_MarginDemoRight = GetPrivateProfileInt(L"Margin", L"DemoRight", 24, theme); + +#elif SUISHO_SHIZUKU_SUPPORT + m_MarginButtonTop = GetPrivateProfileInt(L"Margin", L"ButtonTop", 8, theme); + m_MarginButtonLeft = GetPrivateProfileInt(L"Margin", L"ButtonLeft", 0, theme); + m_MarginButtonBottom = GetPrivateProfileInt(L"Margin", L"ButtonBottom", 8, theme); + m_MarginButtonRight = GetPrivateProfileInt(L"Margin", L"ButtonRight", 0, theme); + m_MarginMeterTop = GetPrivateProfileInt(L"Margin", L"MeterTop", 0, theme); + m_MarginMeterLeft = GetPrivateProfileInt(L"Margin", L"MeterLeft", 0, theme); + m_MarginMeterBottom = GetPrivateProfileInt(L"Margin", L"MeterBottom", 0, theme); + m_MarginMeterRight = GetPrivateProfileInt(L"Margin", L"MeterRight", 16, theme); + m_MarginCommentTop = GetPrivateProfileInt(L"Margin", L"CommentTop", 0, theme); + m_MarginCommentLeft = GetPrivateProfileInt(L"Margin", L"CommentLeft", 16, theme); + m_MarginCommentBottom = GetPrivateProfileInt(L"Margin", L"CommentBottom", 0, theme); + m_MarginCommentRight = GetPrivateProfileInt(L"Margin", L"CommentRight", 16, theme); + m_MarginDemoTop = GetPrivateProfileInt(L"Margin", L"DemoTop", 24, theme); + m_MarginDemoLeft = GetPrivateProfileInt(L"Margin", L"DemoLeft", 24, theme); + m_MarginDemoBottom = GetPrivateProfileInt(L"Margin", L"DemoBottom", 24, theme); + m_MarginDemoRight = GetPrivateProfileInt(L"Margin", L"DemoRight", 24, theme); +#else + m_MarginButtonTop = GetPrivateProfileInt(L"Margin", L"ButtonTop", 4, theme); + m_MarginButtonLeft = GetPrivateProfileInt(L"Margin", L"ButtonLeft", 0, theme); + m_MarginButtonBottom = GetPrivateProfileInt(L"Margin", L"ButtonBottom", 4, theme); + m_MarginButtonRight = GetPrivateProfileInt(L"Margin", L"ButtonRight", 0, theme); + m_MarginMeterTop = GetPrivateProfileInt(L"Margin", L"MeterTop", 0, theme); + m_MarginMeterLeft = GetPrivateProfileInt(L"Margin", L"MeterLeft", 0, theme); + m_MarginMeterBottom = GetPrivateProfileInt(L"Margin", L"MeterBottom", 0, theme); + m_MarginMeterRight = GetPrivateProfileInt(L"Margin", L"MeterRight", 4, theme); + m_MarginCommentTop = GetPrivateProfileInt(L"Margin", L"CommentTop", 0, theme); + m_MarginCommentLeft = GetPrivateProfileInt(L"Margin", L"CommentLeft", 8, theme); + m_MarginCommentBottom = GetPrivateProfileInt(L"Margin", L"CommentBottom", 0, theme); + m_MarginCommentRight = GetPrivateProfileInt(L"Margin", L"CommentRight", 8, theme); + m_MarginDemoTop = GetPrivateProfileInt(L"Margin", L"DemoTop", 8, theme); + m_MarginDemoLeft = GetPrivateProfileInt(L"Margin", L"DemoLeft", 8, theme); + m_MarginDemoBottom = GetPrivateProfileInt(L"Margin", L"DemoBottom", 8, theme); + m_MarginDemoRight = GetPrivateProfileInt(L"Margin", L"DemoRight", 8, theme); +#endif +} + + +CDiskMarkDlg::~CDiskMarkDlg() +{ +} + +void CDiskMarkDlg::DoDataExchange(CDataExchange* pDX) +{ + CMainDialogFx::DoDataExchange(pDX); + + DDX_Control(pDX, IDC_BUTTON_ALL, m_ButtonAll); + DDX_Control(pDX, IDC_BUTTON_TEST_0, m_ButtonTest0); + DDX_Control(pDX, IDC_BUTTON_TEST_1, m_ButtonTest1); + DDX_Control(pDX, IDC_BUTTON_TEST_2, m_ButtonTest2); + DDX_Control(pDX, IDC_BUTTON_TEST_3, m_ButtonTest3); + + DDX_Control(pDX, IDC_TEST_READ_0, m_TestRead0); + DDX_Control(pDX, IDC_TEST_READ_1, m_TestRead1); + DDX_Control(pDX, IDC_TEST_READ_2, m_TestRead2); + DDX_Control(pDX, IDC_TEST_READ_3, m_TestRead3); + + DDX_Control(pDX, IDC_TEST_WRITE_0, m_TestWrite0); + DDX_Control(pDX, IDC_TEST_WRITE_1, m_TestWrite1); + DDX_Control(pDX, IDC_TEST_WRITE_2, m_TestWrite2); + DDX_Control(pDX, IDC_TEST_WRITE_3, m_TestWrite3); + +#ifdef MIX_MODE + DDX_Control(pDX, IDC_TEST_MIX_0, m_TestMix0); + DDX_Control(pDX, IDC_TEST_MIX_1, m_TestMix1); + DDX_Control(pDX, IDC_TEST_MIX_2, m_TestMix2); + DDX_Control(pDX, IDC_TEST_MIX_3, m_TestMix3); + DDX_Control(pDX, IDC_COMBO_MIX, m_ComboMix); +#endif + + DDX_Control(pDX, IDC_COMMENT, m_Comment); + + DDX_Control(pDX, IDC_COMBO_COUNT, m_ComboCount); + DDX_Control(pDX, IDC_COMBO_SIZE, m_ComboSize); + DDX_Control(pDX, IDC_COMBO_DRIVE, m_ComboDrive); + DDX_Control(pDX, IDC_COMBO_UNIT, m_ComboUnit); + + DDX_Control(pDX, IDC_DEMO_SETTING, m_DemoSetting); + DDX_Control(pDX, IDC_READ_UNIT, m_ReadUnit); + DDX_Control(pDX, IDC_WRITE_UNIT, m_WriteUnit); + +#ifdef MIX_MODE + DDX_Control(pDX, IDC_MIX_UNIT, m_MixUnit); +#endif + + DDX_Text(pDX, IDC_COMBO_COUNT, m_ValueTestCount); + DDX_Text(pDX, IDC_COMBO_SIZE, m_ValueTestSize); + DDX_Text(pDX, IDC_COMBO_DRIVE, m_ValueTestDrive); + DDX_Text(pDX, IDC_COMBO_UNIT, m_ValueTestUnit); + DDX_CBIndex(pDX, IDC_COMBO_COUNT, m_IndexTestCount); + DDX_CBIndex(pDX, IDC_COMBO_SIZE, m_IndexTestSize); + DDX_CBIndex(pDX, IDC_COMBO_DRIVE, m_IndexTestDrive); + DDX_CBIndex(pDX, IDC_COMBO_UNIT, m_IndexTestUnit); +#ifdef MIX_MODE + DDX_CBIndex(pDX, IDC_COMBO_MIX, m_IndexTestMix); +#endif +} + +BEGIN_MESSAGE_MAP(CDiskMarkDlg, CMainDialogFx) + //}}AFX_MSG_MAP +#ifdef SUISHO_SHIZUKU_SUPPORT + ON_WM_GETMINMAXINFO() + ON_WM_SIZE() +#endif + ON_COMMAND(ID_EXIT, OnExit) + ON_COMMAND(ID_ABOUT, OnAbout) + ON_COMMAND(ID_COPY, OnCopy) + ON_MESSAGE(WM_UPDATE_SCORE, OnUpdateScore) + ON_MESSAGE(WM_UPDATE_MESSAGE, OnUpdateMessage) + ON_MESSAGE(WM_EXIT_BENCHMARK, OnExitBenchmark) + ON_WM_LBUTTONDOWN() + + ON_COMMAND(ID_ZOOM_100, &CDiskMarkDlg::OnZoom100) + ON_COMMAND(ID_ZOOM_125, &CDiskMarkDlg::OnZoom125) + ON_COMMAND(ID_ZOOM_150, &CDiskMarkDlg::OnZoom150) + ON_COMMAND(ID_ZOOM_200, &CDiskMarkDlg::OnZoom200) + ON_COMMAND(ID_ZOOM_250, &CDiskMarkDlg::OnZoom250) + ON_COMMAND(ID_ZOOM_300, &CDiskMarkDlg::OnZoom300) + ON_COMMAND(ID_ZOOM_AUTO, &CDiskMarkDlg::OnZoomAuto) + + ON_COMMAND(ID_HELP, &CDiskMarkDlg::OnHelp) + ON_COMMAND(ID_CRYSTALDEWWORLD, &CDiskMarkDlg::OnCrystalDewWorld) + ON_COMMAND(ID_MODE_DEFAULT, &CDiskMarkDlg::OnModeDefault) + ON_COMMAND(ID_MODE_ALL0X00, &CDiskMarkDlg::OnModeAll0x00) + + ON_COMMAND(ID_SETTING_DEFAULT, &CDiskMarkDlg::OnSettingDefault) + ON_COMMAND(ID_SETTING_NVME_8, &CDiskMarkDlg::OnSettingNVMe8) + ON_COMMAND(ID_SETTING_FLASH_MEMORY, &CDiskMarkDlg::OnSettingFlashMemory) + + ON_COMMAND(ID_PROFILE_DEFAULT, &CDiskMarkDlg::OnProfileDefault) + ON_COMMAND(ID_PROFILE_REAL, &CDiskMarkDlg::OnProfileReal) + ON_COMMAND(ID_PROFILE_PEAK, &CDiskMarkDlg::OnProfilePeak) + ON_COMMAND(ID_PROFILE_DEMO, &CDiskMarkDlg::OnProfileDemo) + +#ifdef MIX_MODE + ON_COMMAND(ID_PROFILE_DEFAULT_MIX, &CDiskMarkDlg::OnProfileDefaultMix) + ON_COMMAND(ID_PROFILE_REAL_MIX, &CDiskMarkDlg::OnProfileRealMix) + ON_COMMAND(ID_PROFILE_PEAK_MIX, &CDiskMarkDlg::OnProfilePeakMix) +#endif + + ON_COMMAND(ID_BENCHMARK_READ_WRITE, &CDiskMarkDlg::OnBenchmarkReadWrite) + ON_COMMAND(ID_BENCHMARK_READ_ONLY, &CDiskMarkDlg::OnBenchmarkReadOnly) + ON_COMMAND(ID_BENCHMARK_WRITE_ONLY, &CDiskMarkDlg::OnBenchmarkWriteOnly) + + //}}AFX_MSG_MAP + ON_COMMAND(ID_SAVE_TEXT, &CDiskMarkDlg::OnSaveText) + ON_COMMAND(ID_SAVE_IMAGE, &CDiskMarkDlg::OnSaveImage) + ON_COMMAND(ID_SETTINGS_QUEUESTHREADS, &CDiskMarkDlg::OnSettingsQueuesThreads) + ON_COMMAND(ID_FONT_SETTING, &CDiskMarkDlg::OnFontSetting) + ON_WM_NCCREATE() + ON_MESSAGE(WM_QUERYENDSESSION, &CDiskMarkDlg::OnQueryEndSession) + + ON_BN_CLICKED(IDC_BUTTON_ALL, &CDiskMarkDlg::OnAll) + ON_BN_CLICKED(IDC_BUTTON_TEST_0, &CDiskMarkDlg::OnTest0) + ON_BN_CLICKED(IDC_BUTTON_TEST_1, &CDiskMarkDlg::OnTest1) + ON_BN_CLICKED(IDC_BUTTON_TEST_2, &CDiskMarkDlg::OnTest2) + ON_BN_CLICKED(IDC_BUTTON_TEST_3, &CDiskMarkDlg::OnTest3) + ON_CBN_SELCHANGE(IDC_COMBO_DRIVE, &CDiskMarkDlg::OnCbnSelchangeComboDrive) + ON_CBN_SELCHANGE(IDC_COMBO_UNIT, &CDiskMarkDlg::OnCbnSelchangeComboUnit) +#ifdef MIX_MODE + ON_CBN_SELCHANGE(IDC_COMBO_MIX, &CDiskMarkDlg::OnCbnSelchangeComboMix) +#endif + +END_MESSAGE_MAP() + +LRESULT CDiskMarkDlg::OnQueryEndSession(WPARAM wParam, LPARAM lParam) +{ + return TRUE; +} + +BOOL CDiskMarkDlg::CheckThemeEdition(CString name) +{ +#ifdef SUISHO_AOI_SUPPORT + if (name.Find(L"Aoi") == 0) { return TRUE; } +#elif MSI_MEI_SUPPORT + if (name.Find(L"MSIMei") == 0) { return TRUE; } +#elif SUISHO_SHIZUKU_SUPPORT + if(name.Find(L"Shizuku") == 0) { return TRUE; } +#elif KUREI_KEI_SUPPORT + if(name.Find(L"KureiKei") == 0) { return TRUE; } +#else + if(name.Find(L"Shizuku") != 0 && name.Find(L"Aoi") != 0 && name.Find(L"MSIMei") != 0 && name.Find(L"Tokka") != 0 && name.Find(L"KureiKei") != 0 && name.Find(L".") != 0) { return TRUE; } +#endif + + return FALSE; +} + +int CALLBACK EnumFontFamExProcDefaultFont(ENUMLOGFONTEX* lpelfe, NEWTEXTMETRICEX* lpntme, int FontType, LPARAM lParam) +{ + if (_tcscmp(lpelfe->elfLogFont.lfFaceName, DEFAULT_FONT_FACE_1) == 0) + { + *((BOOL*)lParam) = TRUE; + } + return TRUE; +} + +BOOL CDiskMarkDlg::IsDefaultMode() +{ + if (m_MeasureTime == 5 + && m_BenchSize[0] == 1024 && m_BenchQueues[0] == 8 && m_BenchThreads[0] == 1 && m_BenchType[0] == BENCH_SEQ + && m_BenchSize[1] == 1024 && m_BenchQueues[1] == 1 && m_BenchThreads[1] == 1 && m_BenchType[1] == BENCH_SEQ + && m_BenchSize[2] == 4 && m_BenchQueues[2] == 32 && m_BenchThreads[2] == 1 && m_BenchType[2] == BENCH_RND + && m_BenchSize[3] == 4 && m_BenchQueues[3] == 1 && m_BenchThreads[3] == 1 && m_BenchType[3] == BENCH_RND + && m_BenchSize[4] == 1024 && m_BenchQueues[4] == 8 && m_BenchThreads[4] == 1 && m_BenchType[4] == BENCH_SEQ + && m_BenchSize[5] == 4 && m_BenchQueues[5] == 32 && m_BenchThreads[5] == 1 && m_BenchType[5] == BENCH_RND + && m_BenchSize[8] == 1024 && m_BenchQueues[8] == 8 && m_BenchThreads[8] == 1 && m_BenchType[8] == BENCH_SEQ + ) + { + return TRUE; + } + return FALSE; +} + +BOOL CDiskMarkDlg::IsNVMe8Mode() +{ + if (m_MeasureTime == 5 + && m_BenchSize[0] == 1024 && m_BenchQueues[0] == 8 && m_BenchThreads[0] == 1 && m_BenchType[0] == BENCH_SEQ + && m_BenchSize[1] == 128 && m_BenchQueues[1] == 32 && m_BenchThreads[1] == 1 && m_BenchType[1] == BENCH_SEQ + && m_BenchSize[2] == 4 && m_BenchQueues[2] == 32 && m_BenchThreads[2] == 16 && m_BenchType[2] == BENCH_RND + && m_BenchSize[3] == 4 && m_BenchQueues[3] == 1 && m_BenchThreads[3] == 1 && m_BenchType[3] == BENCH_RND + && m_BenchSize[4] == 1024 && m_BenchQueues[4] == 8 && m_BenchThreads[4] == 1 && m_BenchType[4] == BENCH_SEQ + && m_BenchSize[5] == 4 && m_BenchQueues[5] == 32 && m_BenchThreads[5] == 16 && m_BenchType[5] == BENCH_RND + && m_BenchSize[8] == 1024 && m_BenchQueues[8] == 8 && m_BenchThreads[8] == 1 && m_BenchType[8] == BENCH_SEQ + ) + { + return TRUE; + } + return FALSE; +} + + +BOOL CDiskMarkDlg::IsFlashMemoryMode() +{ + if (m_MeasureTime == 1 + && m_BenchSize[0] == 1024 && m_BenchQueues[0] == 8 && m_BenchThreads[0] == 1 && m_BenchType[0] == BENCH_SEQ + && m_BenchSize[1] == 1024 && m_BenchQueues[1] == 1 && m_BenchThreads[1] == 1 && m_BenchType[1] == BENCH_SEQ + && m_BenchSize[2] == 4 && m_BenchQueues[2] == 32 && m_BenchThreads[2] == 1 && m_BenchType[2] == BENCH_RND + && m_BenchSize[3] == 4 && m_BenchQueues[3] == 1 && m_BenchThreads[3] == 1 && m_BenchType[3] == BENCH_RND + && m_BenchSize[4] == 1024 && m_BenchQueues[4] == 8 && m_BenchThreads[4] == 1 && m_BenchType[4] == BENCH_SEQ + && m_BenchSize[5] == 4 && m_BenchQueues[5] == 32 && m_BenchThreads[5] == 1 && m_BenchType[5] == BENCH_RND + && m_BenchSize[8] == 1024 && m_BenchQueues[8] == 8 && m_BenchThreads[8] == 1 && m_BenchType[8] == BENCH_SEQ + ) + { + return TRUE; + } + return FALSE; +} + +BOOL CDiskMarkDlg::OnInitDialog() +{ + CMainDialogFx::OnInitDialog(); + + m_hAccelerator = ::LoadAccelerators(AfxGetInstanceHandle(), + MAKEINTRESOURCE(IDR_ACCELERATOR)); + + SetIcon(m_hIcon, TRUE); + SetIcon(m_hIconMini, FALSE); + + TCHAR str[256]; + + CClientDC dc(this); + LOGFONT logfont; + CString defaultFontFace; + BOOL hasDefaultFont = FALSE; + ZeroMemory(&logfont, sizeof(LOGFONT)); + logfont.lfCharSet = DEFAULT_CHARSET; + ::EnumFontFamiliesExW(dc.m_hDC, &logfont, (FONTENUMPROC)EnumFontFamExProcDefaultFont, (INT_PTR)(&hasDefaultFont), 0); + + if (hasDefaultFont) + { + defaultFontFace = DEFAULT_FONT_FACE_1; + } + else + { + defaultFontFace = DEFAULT_FONT_FACE_2; + } + + GetPrivateProfileString(L"Setting", L"FontFace", defaultFontFace, str, 256, m_Ini); + m_FontFace = str; + + m_TestData = GetPrivateProfileInt(L"Setting", L"TestData", TEST_DATA_RANDOM, m_Ini); + if (m_TestData != TEST_DATA_ALL0X00) + { + m_TestData = TEST_DATA_RANDOM; + } + + m_Profile = GetPrivateProfileInt(L"Setting", L"Profile", PROFILE_DEFAULT, m_Ini); + +#ifdef MIX_MODE + if (PROFILE_DEFAULT > m_Profile || m_Profile > PROFILE_REAL_MIX) +#else + if (PROFILE_DEFAULT > m_Profile || m_Profile > PROFILE_DEMO) +#endif + { + m_Profile = PROFILE_DEFAULT; + } + + m_Benchmark = GetPrivateProfileInt(L"Setting", L"Benchmark", BENCHMARK_READ_WRITE, m_Ini); + if (BENCHMARK_READ > m_Benchmark || m_Benchmark > BENCHMARK_READ_WRITE) + { + m_Benchmark = BENCHMARK_READ_WRITE; + } + + if (m_Profile == PROFILE_DEFAULT_MIX || m_Profile == PROFILE_PEAK_MIX || m_Profile == PROFILE_REAL_MIX) + { + m_MixMode = TRUE; + } + else + { + m_MixMode = FALSE; + } + + m_FontScale = GetPrivateProfileInt(L"Setting", L"FontScale", 100, m_Ini); + if (m_FontScale > 200 || m_FontScale < 50) + { + m_FontScale = 100; + m_FontRatio = 1.0; + } + else + { + m_FontRatio = m_FontScale / 100.0; + } + + m_FontRender = GetPrivateProfileInt(L"Setting", L"FontRender", CLEARTYPE_NATURAL_QUALITY, m_Ini); + if (m_FontRender > CLEARTYPE_NATURAL_QUALITY) + { + m_FontRender = CLEARTYPE_NATURAL_QUALITY; + } + + // Unit + m_ComboUnit.AddString(L"MB/s"); + m_ComboUnit.AddString(L"GB/s"); + m_ComboUnit.AddString(L"IOPS"); + m_ComboUnit.AddString(L"μs"); + + // Count + for (int i = 1; i < 10; i++) + { + CString cstr; + cstr.Format(L"%d", i); + m_ComboCount.AddString(cstr); + } + +#ifdef MIX_MODE + // Mix + for (int i = 1; i < 10; i++) + { + CString cstr; + cstr.Format(L"R%d%%/W%d%%", i * 10, 100 - i*10); + m_ComboMix.AddString(cstr); + } +#endif + + m_WinThread = NULL; + m_DiskBenchStatus = FALSE; + + InitThemeLang(); + InitMenu(); + UpdateThemeInfo(); + ChangeLang(m_CurrentLang); + + UpdateQueuesThreads(); + + m_IndexTestCount = GetPrivateProfileInt(L"Setting", L"TestCount", 2, m_Ini); + if (m_IndexTestCount < 0 || m_IndexTestCount >= 9) + { + m_IndexTestCount = 2; // default value is 3. + } + m_ComboCount.SetCurSel(m_IndexTestCount); + + // Size + TCHAR size[13][8] = { L"16MiB", L"32MiB", L"64MiB", L"128MiB", L"256MiB", L"512MiB", L"1GiB", L"2GiB", L"4GiB", L"8GiB", L"16GiB", L"32GiB", L"64GiB" }; + + for (int i = 0; i < 13; i++) + { + CString cstr; + cstr.Format(L"%s", size[i]); + m_ComboSize.AddString(cstr); + } + m_IndexTestSize = GetPrivateProfileInt(L"Setting", L"TestSize", 6, m_Ini); + if (m_IndexTestSize < 0 || m_IndexTestSize > 13) + { + m_IndexTestSize = 6; // default value is 1GiB; + } + m_ComboSize.SetCurSel(m_IndexTestSize); + + m_IndexTestUnit = GetPrivateProfileInt(L"Setting", L"TestUnit", 0, m_Ini); + if (m_IndexTestUnit < 0 || m_IndexTestUnit >= 3) + { + m_IndexTestUnit = 0; + } + m_ComboUnit.SetCurSel(m_IndexTestUnit); + + m_IndexTestMix = GetPrivateProfileInt(L"Setting", L"TestMix", 6, m_Ini); + if (m_IndexTestMix < 0 || m_IndexTestMix > 10) + { + m_IndexTestMix = 6; // default retio is R70W30; + } + m_MixRatio = (9 - m_IndexTestMix) * 10; + +#ifdef MIX_MODE + m_ComboMix.SetCurSel(m_IndexTestMix); +#endif + + UpdateData(FALSE); + + // Drive + InitDrive(); + + InitScore(); + UpdateUnitLabel(); + + switch(GetPrivateProfileInt(L"Setting", L"ZoomType", 0, m_Ini)) + { + case 100: CheckRadioZoomType(ID_ZOOM_100, 100); break; + case 125: CheckRadioZoomType(ID_ZOOM_125, 125); break; + case 150: CheckRadioZoomType(ID_ZOOM_150, 150); break; + case 200: CheckRadioZoomType(ID_ZOOM_200, 200); break; + case 250: CheckRadioZoomType(ID_ZOOM_250, 250); break; + case 300: CheckRadioZoomType(ID_ZOOM_300, 300); break; + default: CheckRadioZoomType(ID_ZOOM_AUTO, 0); break; + } + + ChangeZoomType(m_ZoomType); + + m_SizeX = SIZE_X; + m_SizeY = SIZE_Y; + +#ifdef MIX_MODE + if (m_MixMode) + { + m_SizeX = SIZE_X_MIX; + } +#endif + + SetWindowTitle(L""); + + SetClientSize(m_SizeX, m_SizeY, m_ZoomRatio); + + m_bShowWindow = TRUE; + + CenterWindow(); + + UpdateDialogSize(); + ChangeButtonStatus(TRUE); + + m_bInitializing = FALSE; + + SetForegroundWindow(); + + return TRUE; +} + +void CDiskMarkDlg::UpdateDialogSize() +{ + CDialogFx::UpdateDialogSize(); + m_bHighContrast = FALSE; + + ShowWindow(SW_HIDE); + int offsetX = 0; + int comboDriveX = 0; + + m_SizeX = SIZE_X; + m_SizeY = SIZE_Y; +#ifdef MIX_MODE + if (m_MixMode) + { + m_SizeX = SIZE_X_MIX; + } +#endif + SetClientSize(m_SizeX, m_SizeY, m_ZoomRatio); + +#ifdef SUISHO_SHIZUKU_SUPPORT + if (m_CharacterPosition == 0) + { + offsetX = OFFSET_X; + } +#endif + + UpdateBackground(TRUE, m_bDarkMode); + SetControlFont(); + + if (m_Profile != PROFILE_DEFAULT && m_Profile != PROFILE_DEFAULT_MIX) + { +#ifdef SUISHO_SHIZUKU_SUPPORT + comboDriveX = 120; +#else + comboDriveX = 72; +#endif + } + else + { + comboDriveX = 0; + } + + m_TestRead0.SetDrawFrame(m_bHighContrast); + m_TestRead1.SetDrawFrame(m_bHighContrast); + m_TestRead2.SetDrawFrame(m_bHighContrast); + m_TestRead3.SetDrawFrame(m_bHighContrast); + m_TestWrite0.SetDrawFrame(m_bHighContrast); + m_TestWrite1.SetDrawFrame(m_bHighContrast); + m_TestWrite2.SetDrawFrame(m_bHighContrast); + m_TestWrite3.SetDrawFrame(m_bHighContrast); + m_Comment.SetDrawFrame(m_bHighContrast); + +#ifdef MIX_MODE + m_TestMix0.SetDrawFrame(m_bHighContrast); + m_TestMix1.SetDrawFrame(m_bHighContrast); + m_TestMix2.SetDrawFrame(m_bHighContrast); + m_TestMix3.SetDrawFrame(m_bHighContrast); +#endif + + if (m_Profile == PROFILE_DEMO) + { + m_ReadUnit.ShowWindow(SW_HIDE); + m_WriteUnit.ShowWindow(SW_HIDE); + + m_TestRead1.ShowWindow(SW_HIDE); + m_TestRead2.ShowWindow(SW_HIDE); + m_TestRead3.ShowWindow(SW_HIDE); + m_TestWrite1.ShowWindow(SW_HIDE); + m_TestWrite2.ShowWindow(SW_HIDE); + m_TestWrite3.ShowWindow(SW_HIDE); + + m_DemoSetting.ShowWindow(SW_SHOW); + } + else + { + m_ReadUnit.ShowWindow(SW_SHOW); + m_WriteUnit.ShowWindow(SW_SHOW); + + m_TestRead1.ShowWindow(SW_SHOW); + m_TestRead2.ShowWindow(SW_SHOW); + m_TestRead3.ShowWindow(SW_SHOW); + m_TestWrite1.ShowWindow(SW_SHOW); + m_TestWrite2.ShowWindow(SW_SHOW); + m_TestWrite3.ShowWindow(SW_SHOW); + + m_DemoSetting.ShowWindow(SW_HIDE); + } + + if (m_Profile == PROFILE_DEMO) + { +#ifdef SUISHO_SHIZUKU_SUPPORT + m_ButtonAll.InitControl(12 + offsetX, 8, 120, 80, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Button"), 3, BS_CENTER, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_ButtonAll.SetHandCursor(TRUE); +/* + m_TestRead0.SetGlassColor(m_Glass, m_GlassAlpha); + m_TestWrite0.SetGlassColor(m_Glass, m_GlassAlpha); + + m_TestRead0.InitControl(12 + offsetX, 96, 384, 348, m_ZoomRatio, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawGlass, m_bHighContrast, FALSE); + m_TestWrite0.InitControl(404 + offsetX, 96, 384, 348, m_ZoomRatio, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawGlass, m_bHighContrast, FALSE); +*/ +#ifdef SUISHO_AOI_SUPPORT + m_TestRead0.InitControl(12 + offsetX, 96, 384, 344, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Demo"), 1, SS_CENTER, OwnerDrawImage, FALSE, FALSE, FALSE); + m_TestWrite0.InitControl(404 + offsetX, 96, 384, 344, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Demo"), 1, SS_CENTER, OwnerDrawImage, FALSE, FALSE, FALSE); + m_Comment.InitControl(12 + offsetX, 440, 776, 60, m_ZoomRatio, &m_BkDC, IP(L"Comment"), 1, ES_LEFT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); +#else + m_TestRead0.InitControl(12 + offsetX, 96, 384, 348, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Demo"), 1, SS_CENTER, OwnerDrawImage, FALSE, FALSE, FALSE); + m_TestWrite0.InitControl(404 + offsetX, 96, 384, 348, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Demo"), 1, SS_CENTER, OwnerDrawImage, FALSE, FALSE, FALSE); + m_Comment.InitControl(12 + offsetX, 452, 776, 40, m_ZoomRatio, &m_BkDC, IP(L"Comment"), 1, ES_LEFT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); +#endif + m_Comment.SetMargin(m_MarginCommentTop, m_MarginCommentLeft, m_MarginCommentBottom, m_MarginCommentRight, m_ZoomRatio); + m_Comment.Adjust(); + + m_DemoSetting.InitControl(140 + offsetX, 56, 528, 40, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, m_bHighContrast, FALSE, FALSE); + m_ReadUnit.InitControl(12 + offsetX, 96, 120, 32, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, m_bHighContrast, FALSE, FALSE); + m_WriteUnit.InitControl(672 + offsetX, 96, 116, 32, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, m_bHighContrast, FALSE, FALSE); + + m_ComboCount.InitControl(140 + offsetX, 8, 60, 500, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawGlass, m_bHighContrast, FALSE, m_ComboBk, m_ComboBkSelected, m_Glass, m_GlassAlpha); + m_ComboSize.InitControl(204 + offsetX, 8, 140, 500, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawGlass, m_bHighContrast, FALSE, m_ComboBk, m_ComboBkSelected, m_Glass, m_GlassAlpha); + m_ComboDrive.InitControl(348 + offsetX, 8, 320, 500, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawGlass, m_bHighContrast, FALSE, m_ComboBk, m_ComboBkSelected, m_Glass, m_GlassAlpha); + m_ComboUnit.InitControl(672 + offsetX, 8, 116, 500, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawGlass, m_bHighContrast, FALSE, m_ComboBk, m_ComboBkSelected, m_Glass, m_GlassAlpha); +#else + m_TestRead0.SetDrawFrameEx(TRUE, m_Frame); + m_TestWrite0.SetDrawFrameEx(TRUE, m_Frame); + + m_ButtonAll.InitControl(8 + offsetX, 8, 72, 48, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Button"), 3, BS_CENTER, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_ButtonAll.SetHandCursor(TRUE); + + m_TestRead0.SetGlassColor(m_Glass, m_GlassAlpha); + m_TestWrite0.SetGlassColor(m_Glass, m_GlassAlpha); + + m_TestRead0.InitControl(8 + offsetX, 64, 228, 196, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawGlass, m_bHighContrast, TRUE, FALSE); + m_TestWrite0.InitControl(244 + offsetX, 64, 228, 196, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawGlass, m_bHighContrast, TRUE, FALSE); + + m_Comment.InitControl(8 + offsetX, 268, 464, 24, m_ZoomRatio, &m_BkDC, IP(L"Comment"), 1, ES_LEFT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_Comment.SetMargin(m_MarginCommentTop, m_MarginCommentLeft, m_MarginCommentBottom, m_MarginCommentRight, m_ZoomRatio); + m_Comment.Adjust(); + + m_DemoSetting.InitControl(84 + offsetX, 36, 320, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, m_bHighContrast, FALSE, FALSE); + m_ReadUnit.InitControl(84 + offsetX, 36, 124, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, m_bHighContrast, FALSE, FALSE); + m_WriteUnit.InitControl(280 + offsetX, 36, 124, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, m_bHighContrast, FALSE, FALSE); + + m_ComboCount.InitControl(84 + offsetX, 8, 40, 300, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawGlass, m_bHighContrast, FALSE, m_ComboBk, m_ComboBkSelected, m_Glass, m_GlassAlpha); + m_ComboSize.InitControl(128 + offsetX, 8, 80, 300, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawGlass, m_bHighContrast, FALSE, m_ComboBk, m_ComboBkSelected, m_Glass, m_GlassAlpha); + m_ComboDrive.InitControl(212 + offsetX, 8, 188, 300, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawGlass, m_bHighContrast, FALSE, m_ComboBk, m_ComboBkSelected, m_Glass, m_GlassAlpha); + m_ComboUnit.InitControl(404 + offsetX, 8, 68, 300, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawGlass, m_bHighContrast, FALSE, m_ComboBk, m_ComboBkSelected, m_Glass, m_GlassAlpha); +#endif + } + else + { +#ifdef SUISHO_SHIZUKU_SUPPORT + m_ButtonAll.InitControl(12 + offsetX, 8, 120, 80, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Button"), 3, BS_CENTER, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_ButtonTest0.InitControl(12 + offsetX, 96, 120, 80, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Button"), 3, BS_CENTER, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_ButtonTest1.InitControl(12 + offsetX, 184, 120, 80, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Button"), 3, BS_CENTER, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_ButtonTest2.InitControl(12 + offsetX, 272, 120, 80, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Button"), 3, BS_CENTER, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_ButtonTest3.InitControl(12 + offsetX, 360, 120, 80, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Button"), 3, BS_CENTER, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + + m_ButtonAll.SetHandCursor(TRUE); + m_ButtonTest0.SetHandCursor(TRUE); + m_ButtonTest1.SetHandCursor(TRUE); + m_ButtonTest2.SetHandCursor(TRUE); + m_ButtonTest3.SetHandCursor(TRUE); + + m_TestRead0.InitControl(140 + offsetX, 96, 320, 80, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Meter"), 2, SS_RIGHT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_TestRead1.InitControl(140 + offsetX, 184, 320, 80, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Meter"), 2, SS_RIGHT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_TestRead2.InitControl(140 + offsetX, 272, 320, 80, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Meter"), 2, SS_RIGHT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_TestRead3.InitControl(140 + offsetX, 360, 320, 80, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Meter"), 2, SS_RIGHT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + + m_TestWrite0.InitControl(468 + offsetX, 96, 320, 80, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Meter"), 2, SS_RIGHT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_TestWrite1.InitControl(468 + offsetX, 184, 320, 80, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Meter"), 2, SS_RIGHT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_TestWrite2.InitControl(468 + offsetX, 272, 320, 80, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Meter"), 2, SS_RIGHT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_TestWrite3.InitControl(468 + offsetX, 360, 320, 80, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Meter"), 2, SS_RIGHT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + +#ifdef SUISHO_AOI_SUPPORT + m_Comment.InitControl(12 + offsetX, 440, 776, 60, m_ZoomRatio, &m_BkDC, IP(L"Comment"), 1, ES_LEFT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); +#else + m_Comment.InitControl(12 + offsetX, 452, 776, 40, m_ZoomRatio, &m_BkDC, IP(L"Comment"), 1, ES_LEFT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); +#endif + + m_Comment.SetMargin(m_MarginCommentTop, m_MarginCommentLeft, m_MarginCommentBottom, m_MarginCommentRight, m_ZoomRatio); + m_Comment.Adjust(); + + m_ReadUnit.InitControl(140 + offsetX, 56, 320, 40, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, m_bHighContrast, FALSE, FALSE); + m_WriteUnit.InitControl(468 + offsetX, 56, 320, 40, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, m_bHighContrast, FALSE, FALSE); + + m_ComboCount.InitControl(140 + offsetX, 8, 60, 500, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawGlass, m_bHighContrast, FALSE, m_ComboBk, m_ComboBkSelected, m_Glass, m_GlassAlpha); + m_ComboSize.InitControl(204 + offsetX, 8, 140, 500, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawGlass, m_bHighContrast, FALSE, m_ComboBk, m_ComboBkSelected, m_Glass, m_GlassAlpha); + m_ComboUnit.InitControl(672 + offsetX, 8, 116, 500, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawGlass, m_bHighContrast, FALSE, m_ComboBk, m_ComboBkSelected, m_Glass, m_GlassAlpha); + + if (m_Profile == PROFILE_PEAK || m_Profile == PROFILE_REAL) + { + m_ComboDrive.InitControl(348 + offsetX, 8, 440, 500, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawGlass, m_bHighContrast, FALSE, m_ComboBk, m_ComboBkSelected, m_Glass, m_GlassAlpha); + } + else + { + m_ComboDrive.InitControl(348 + offsetX, 8, 320, 500, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawGlass, m_bHighContrast, FALSE, m_ComboBk, m_ComboBkSelected, m_Glass, m_GlassAlpha); + } +#else + + m_ButtonAll.InitControl(8 + offsetX, 8, 72, 48, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Button"), 3, BS_CENTER, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_ButtonTest0.InitControl(8 + offsetX, 60, 72, 48, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Button"), 3, BS_CENTER, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_ButtonTest1.InitControl(8 + offsetX, 112, 72, 48, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Button"), 3, BS_CENTER, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_ButtonTest2.InitControl(8 + offsetX, 164, 72, 48, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Button"), 3, BS_CENTER, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_ButtonTest3.InitControl(8 + offsetX, 216, 72, 48, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Button"), 3, BS_CENTER, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + + m_ButtonAll.SetHandCursor(TRUE); + m_ButtonTest0.SetHandCursor(TRUE); + m_ButtonTest1.SetHandCursor(TRUE); + m_ButtonTest2.SetHandCursor(TRUE); + m_ButtonTest3.SetHandCursor(TRUE); + + m_TestRead0.InitControl(84 + offsetX, 60, 192, 48, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Meter"), 2, SS_RIGHT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_TestRead1.InitControl(84 + offsetX, 112, 192, 48, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Meter"), 2, SS_RIGHT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_TestRead2.InitControl(84 + offsetX, 164, 192, 48, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Meter"), 2, SS_RIGHT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_TestRead3.InitControl(84 + offsetX, 216, 192, 48, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Meter"), 2, SS_RIGHT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + + m_TestWrite0.InitControl(280 + offsetX, 60, 192, 48, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Meter"), 2, SS_RIGHT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_TestWrite1.InitControl(280 + offsetX, 112, 192, 48, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Meter"), 2, SS_RIGHT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_TestWrite2.InitControl(280 + offsetX, 164, 192, 48, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Meter"), 2, SS_RIGHT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_TestWrite3.InitControl(280 + offsetX, 216, 192, 48, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Meter"), 2, SS_RIGHT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + if (m_MixMode) + { + m_Comment.InitControl(8 + offsetX, 268, 664, 24, m_ZoomRatio, &m_BkDC, IP(L"CommentL"), 1, ES_LEFT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + } + else + { + m_Comment.InitControl(8 + offsetX, 268, 464, 24, m_ZoomRatio, &m_BkDC, IP(L"Comment"), 1, ES_LEFT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + } + m_Comment.SetMargin(m_MarginCommentTop, m_MarginCommentLeft, m_MarginCommentBottom, m_MarginCommentRight, m_ZoomRatio); + m_Comment.Adjust(); + + m_ReadUnit.InitControl(84 + offsetX, 36, 192, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, m_bHighContrast, FALSE, FALSE); + m_WriteUnit.InitControl(280 + offsetX, 36, 192, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, m_bHighContrast, FALSE, FALSE); + + m_ComboCount.InitControl(84 + offsetX, 8, 40, 300, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawGlass, m_bHighContrast, FALSE, m_ComboBk, m_ComboBkSelected, m_Glass, m_GlassAlpha); + m_ComboSize.InitControl(128 + offsetX, 8, 80, 300, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawGlass, m_bHighContrast, FALSE, m_ComboBk, m_ComboBkSelected, m_Glass, m_GlassAlpha); + m_ComboUnit.InitControl(404 + offsetX, 8, 68, 300, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawGlass, m_bHighContrast, FALSE, m_ComboBk, m_ComboBkSelected, m_Glass, m_GlassAlpha); + + if (m_Profile == PROFILE_PEAK || m_Profile == PROFILE_REAL || m_Profile == PROFILE_PEAK_MIX || m_Profile == PROFILE_REAL_MIX) + { + m_ComboDrive.InitControl(212 + offsetX, 8, 260, 300, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawGlass, m_bHighContrast, FALSE, m_ComboBk, m_ComboBkSelected, m_Glass, m_GlassAlpha); + } + else + { + m_ComboDrive.InitControl(212 + offsetX, 8, 188, 300, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawGlass, m_bHighContrast, FALSE, m_ComboBk, m_ComboBkSelected, m_Glass, m_GlassAlpha); + } +#endif + } + + if(m_Profile == PROFILE_DEMO) + { + m_TestRead0.SetMargin(m_MarginDemoTop, m_MarginDemoLeft, m_MarginDemoBottom, m_MarginDemoRight, m_ZoomRatio); + m_TestWrite0.SetMargin(m_MarginDemoTop, m_MarginDemoLeft, m_MarginDemoBottom, m_MarginDemoRight, m_ZoomRatio); + +#ifdef SUISHO_AOI_SUPPORT + m_TestRead0.SetLabelUnitFormat(DT_LEFT | DT_BOTTOM | DT_SINGLELINE, DT_RIGHT | DT_BOTTOM | DT_SINGLELINE); + m_TestWrite0.SetLabelUnitFormat(DT_LEFT | DT_BOTTOM | DT_SINGLELINE, DT_RIGHT | DT_BOTTOM | DT_SINGLELINE); +#endif + } + else + { + m_TestRead0.SetMargin(m_MarginMeterTop, m_MarginMeterLeft, m_MarginMeterBottom, m_MarginMeterRight, m_ZoomRatio); + m_TestRead1.SetMargin(m_MarginMeterTop, m_MarginMeterLeft, m_MarginMeterBottom, m_MarginMeterRight, m_ZoomRatio); + m_TestRead2.SetMargin(m_MarginMeterTop, m_MarginMeterLeft, m_MarginMeterBottom, m_MarginMeterRight, m_ZoomRatio); + m_TestRead3.SetMargin(m_MarginMeterTop, m_MarginMeterLeft, m_MarginMeterBottom, m_MarginMeterRight, m_ZoomRatio); + + m_TestWrite0.SetMargin(m_MarginMeterTop, m_MarginMeterLeft, m_MarginMeterBottom, m_MarginMeterRight, m_ZoomRatio); + m_TestWrite1.SetMargin(m_MarginMeterTop, m_MarginMeterLeft, m_MarginMeterBottom, m_MarginMeterRight, m_ZoomRatio); + m_TestWrite2.SetMargin(m_MarginMeterTop, m_MarginMeterLeft, m_MarginMeterBottom, m_MarginMeterRight, m_ZoomRatio); + m_TestWrite3.SetMargin(m_MarginMeterTop, m_MarginMeterLeft, m_MarginMeterBottom, m_MarginMeterRight, m_ZoomRatio); + } + + m_ComboCount.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboSize.SetMargin (0, 4, 0, 0, m_ZoomRatio); + m_ComboDrive.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboUnit.SetMargin (0, 4, 0, 0, m_ZoomRatio); + +#ifdef MIX_MODE + m_TestMix0.InitControl(480 + offsetX, 60, 192, 48, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Meter"), 2, SS_RIGHT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_TestMix1.InitControl(480 + offsetX, 112, 192, 48, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Meter"), 2, SS_RIGHT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_TestMix2.InitControl(480 + offsetX, 164, 192, 48, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Meter"), 2, SS_RIGHT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_TestMix3.InitControl(480 + offsetX, 216, 192, 48, m_ZoomRatio, m_hPal, &m_BkDC, IP(L"Meter"), 2, SS_RIGHT, OwnerDrawImage, m_bHighContrast, FALSE, FALSE); + m_ComboMix.InitControl(480 + offsetX, 8, 192, 300, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawGlass, m_bHighContrast, FALSE, m_ComboBk, m_ComboBkSelected, m_Glass, m_GlassAlpha); + m_MixUnit.InitControl(480 + offsetX, 36, 192, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_CENTER, OwnerDrawTransparent, m_bHighContrast, FALSE, FALSE); + + m_TestMix0.SetMargin(m_MarginMeterTop, m_MarginMeterLeft, m_MarginMeterBottom, m_MarginMeterRight, m_ZoomRatio); + m_TestMix1.SetMargin(m_MarginMeterTop, m_MarginMeterLeft, m_MarginMeterBottom, m_MarginMeterRight, m_ZoomRatio); + m_TestMix2.SetMargin(m_MarginMeterTop, m_MarginMeterLeft, m_MarginMeterBottom, m_MarginMeterRight, m_ZoomRatio); + m_TestMix3.SetMargin(m_MarginMeterTop, m_MarginMeterLeft, m_MarginMeterBottom, m_MarginMeterRight, m_ZoomRatio); + m_ComboMix.SetMargin(0, 4, 0, 0, m_ZoomRatio); + + if (m_MixMode) + { + m_TestMix0.ShowWindow(SW_SHOW); + m_TestMix1.ShowWindow(SW_SHOW); + m_TestMix2.ShowWindow(SW_SHOW); + m_TestMix3.ShowWindow(SW_SHOW); + m_ComboMix.ShowWindow(SW_SHOW); + m_MixUnit.ShowWindow(SW_SHOW); + } + else + { + m_TestMix0.ShowWindow(SW_HIDE); + m_TestMix1.ShowWindow(SW_HIDE); + m_TestMix2.ShowWindow(SW_HIDE); + m_TestMix3.ShowWindow(SW_HIDE); + m_ComboMix.ShowWindow(SW_HIDE); + m_MixUnit.ShowWindow(SW_HIDE); + } +#endif + + m_Comment.Adjust(); + + UpdateScore(); + + Invalidate(); + + m_ComboCount.ShowWindow(SW_HIDE); + m_ComboSize.ShowWindow(SW_HIDE); + m_ComboDrive.ShowWindow(SW_HIDE); + + COMBOBOXINFO info = { 0 }; + info.cbSize = sizeof(COMBOBOXINFO); + m_ComboCount.GetComboBoxInfo(&info); + SetLayeredWindow(info.hwndList, m_ComboAlpha); + m_ComboSize.GetComboBoxInfo(&info); + SetLayeredWindow(info.hwndList, m_ComboAlpha); + m_ComboDrive.GetComboBoxInfo(&info); + SetLayeredWindow(info.hwndList, m_ComboAlpha); + m_ComboUnit.GetComboBoxInfo(&info); + SetLayeredWindow(info.hwndList, m_ComboAlpha); +#ifdef MIX_MODE + if (m_MixMode) + { + m_ComboMix.GetComboBoxInfo(&info); + SetLayeredWindow(info.hwndList, m_ComboAlpha); + m_ComboMix.ShowWindow(SW_SHOW); + } +#endif + + m_ComboCount.ShowWindow(SW_SHOW); + m_ComboSize.ShowWindow(SW_SHOW); + m_ComboDrive.ShowWindow(SW_SHOW); + + if (m_Profile != PROFILE_DEFAULT && m_Profile != PROFILE_DEFAULT_MIX && m_Profile != PROFILE_DEMO) + { + m_ComboUnit.ShowWindow(SW_HIDE); + } + else + { + m_ComboUnit.ShowWindow(SW_SHOW); + } + + UpdateComboTooltip(); + + ShowWindow(SW_SHOW); +} + +void CDiskMarkDlg::UpdateComboTooltip() +{ + m_ComboCount.SetToolTipText(i18n(L"Title", L"TEST_COUNT")); + m_ComboSize.SetToolTipText(i18n(L"Title", L"TEST_SIZE")); + m_ComboUnit.SetToolTipText(i18n(L"Title", L"TEST_UNIT")); +#ifdef MIX_MODE + if (m_MixMode) + { + m_ComboMix.SetToolTipText(i18n(L"Title", L"TEST_MIX")); + } +#endif +} + +void CDiskMarkDlg::SetLayeredWindow(HWND hWnd, BYTE alpha) +{ + if (IsWin2k()) { return; } + + ::SetWindowLong(hWnd, GWL_EXSTYLE, ::GetWindowLong(hWnd, GWL_EXSTYLE) ^ WS_EX_LAYERED); + ::SetWindowLong(hWnd, GWL_EXSTYLE, ::GetWindowLong(hWnd, GWL_EXSTYLE) | WS_EX_LAYERED); + if (m_bHighContrast) + { + ::SetLayeredWindowAttributes(hWnd, 0, 255, LWA_ALPHA); + } + else + { + ::SetLayeredWindowAttributes(hWnd, 0, alpha, LWA_ALPHA); + } +} + + +void CDiskMarkDlg::SetControlFont() +{ +#ifdef SUISHO_SHIZUKU_SUPPORT + BYTE textAlpha = 255; + COLORREF textColor = RGB(0, 0, 0); +#else + BYTE textAlpha = 255; + COLORREF textColor = RGB(0, 0, 0); +#endif + +#ifdef SUISHO_SHIZUKU_SUPPORT + m_ButtonAll.SetFontEx(m_FontFace, 20, 20, m_ZoomRatio, m_FontRatio, m_ButtonText, FW_BOLD, m_FontRender); + m_ButtonTest0.SetFontEx(m_FontFace, 20, 20, m_ZoomRatio, m_FontRatio, m_ButtonText, FW_BOLD, m_FontRender); + m_ButtonTest1.SetFontEx(m_FontFace, 20, 20, m_ZoomRatio, m_FontRatio, m_ButtonText, FW_BOLD, m_FontRender); + m_ButtonTest2.SetFontEx(m_FontFace, 20, 20, m_ZoomRatio, m_FontRatio, m_ButtonText, FW_BOLD, m_FontRender); + m_ButtonTest3.SetFontEx(m_FontFace, 20, 20, m_ZoomRatio, m_FontRatio, m_ButtonText, FW_BOLD, m_FontRender); + + if (m_Profile == PROFILE_DEMO) + { + m_TestRead0.SetFontEx(m_FontFace, 80, 28, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + m_TestWrite0.SetFontEx(m_FontFace,80, 28, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + } + else + { + m_TestRead0.SetFontEx(m_FontFace, 52, 28, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + m_TestWrite0.SetFontEx(m_FontFace, 52, 28, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + } + + m_TestRead1.SetFontEx(m_FontFace, 52, 28, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + m_TestRead2.SetFontEx(m_FontFace, 52, 28, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + m_TestRead3.SetFontEx(m_FontFace, 52, 28, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + + m_TestWrite1.SetFontEx(m_FontFace, 52, 28, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + m_TestWrite2.SetFontEx(m_FontFace, 52, 28, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + m_TestWrite3.SetFontEx(m_FontFace, 52, 28, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + + m_Comment.SetFontEx(m_FontFace, 28, 28, m_ZoomRatio, m_FontRatio, m_EditText, FW_BOLD, m_FontRender); + + m_ReadUnit.SetFontEx(m_FontFace, 28, 28, m_ZoomRatio, m_FontRatio, m_LabelText, FW_BOLD, m_FontRender); + m_WriteUnit.SetFontEx(m_FontFace, 28, 28, m_ZoomRatio, m_FontRatio, m_LabelText, FW_BOLD, m_FontRender); + m_DemoSetting.SetFontEx(m_FontFace, 28, 28, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + + m_ComboCount.SetFontEx(m_FontFace, 28, 28, m_ZoomRatio, m_FontRatio, m_ComboText, m_ComboTextSelected, FW_NORMAL, m_FontRender); + m_ComboSize.SetFontEx(m_FontFace, 28, 28, m_ZoomRatio, m_FontRatio, m_ComboText, m_ComboTextSelected, FW_NORMAL, m_FontRender); + m_ComboDrive.SetFontEx(m_FontFace, 28, 28, m_ZoomRatio, m_FontRatio, m_ComboText, m_ComboTextSelected, FW_NORMAL, m_FontRender); + m_ComboUnit.SetFontEx(m_FontFace, 28, 28, m_ZoomRatio, m_FontRatio, m_ComboText, m_ComboTextSelected, FW_NORMAL, m_FontRender); + + m_ButtonTest0.SetMargin(m_MarginButtonTop, m_MarginButtonLeft, m_MarginButtonBottom, m_MarginButtonRight, m_ZoomRatio); + m_ButtonTest1.SetMargin(m_MarginButtonTop, m_MarginButtonLeft, m_MarginButtonBottom, m_MarginButtonRight, m_ZoomRatio); + m_ButtonTest2.SetMargin(m_MarginButtonTop, m_MarginButtonLeft, m_MarginButtonBottom, m_MarginButtonRight, m_ZoomRatio); + m_ButtonTest3.SetMargin(m_MarginButtonTop, m_MarginButtonLeft, m_MarginButtonBottom, m_MarginButtonRight, m_ZoomRatio); + + m_ComboCount.SetItemHeightAll(40, m_ZoomRatio, m_FontRatio); + m_ComboSize.SetItemHeightAll(40, m_ZoomRatio, m_FontRatio); + m_ComboDrive.SetItemHeightAll(40, m_ZoomRatio, m_FontRatio); + m_ComboUnit.SetItemHeightAll(40, m_ZoomRatio, m_FontRatio); +#else + m_ButtonAll.SetFontEx(m_FontFace, 12, 16, m_ZoomRatio, m_FontRatio, m_ButtonText, FW_BOLD, m_FontRender); + m_ButtonTest0.SetFontEx(m_FontFace, 12, 16, m_ZoomRatio, m_FontRatio, m_ButtonText, FW_BOLD, m_FontRender); + m_ButtonTest1.SetFontEx(m_FontFace, 12, 16, m_ZoomRatio, m_FontRatio, m_ButtonText, FW_BOLD, m_FontRender); + m_ButtonTest2.SetFontEx(m_FontFace, 12, 16, m_ZoomRatio, m_FontRatio, m_ButtonText, FW_BOLD, m_FontRender); + m_ButtonTest3.SetFontEx(m_FontFace, 12, 16, m_ZoomRatio, m_FontRatio, m_ButtonText, FW_BOLD, m_FontRender); + + if (m_Profile == PROFILE_DEMO) + { + m_TestRead0.SetFontEx(m_FontFace, 48, 16, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + m_TestWrite0.SetFontEx(m_FontFace, 48, 16, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + } + else + { + m_TestRead0.SetFontEx(m_FontFace, 35, 16, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + m_TestWrite0.SetFontEx(m_FontFace, 35, 16, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + } + + m_TestRead1.SetFontEx(m_FontFace, 35, 16, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + m_TestRead2.SetFontEx(m_FontFace, 35, 16, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + m_TestRead3.SetFontEx(m_FontFace, 35, 16, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + + m_TestWrite1.SetFontEx(m_FontFace, 35, 16, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + m_TestWrite2.SetFontEx(m_FontFace, 35, 16, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + m_TestWrite3.SetFontEx(m_FontFace, 35, 16, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + + m_Comment.SetFontEx(m_FontFace, 16, 16, m_ZoomRatio, m_FontRatio, m_EditText, FW_BOLD, m_FontRender); + + m_ReadUnit.SetFontEx(m_FontFace, 16, 16, m_ZoomRatio, m_FontRatio, m_LabelText, FW_BOLD, m_FontRender); + m_WriteUnit.SetFontEx(m_FontFace, 16, 16, m_ZoomRatio, m_FontRatio, m_LabelText, FW_BOLD, m_FontRender); + m_DemoSetting.SetFontEx(m_FontFace, 16, 16, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + + m_ComboCount.SetFontEx(m_FontFace, 16, 16, m_ZoomRatio, m_FontRatio, m_ComboText, m_ComboTextSelected, FW_NORMAL, m_FontRender); + m_ComboSize.SetFontEx(m_FontFace, 16, 16, m_ZoomRatio, m_FontRatio, m_ComboText, m_ComboTextSelected, FW_NORMAL, m_FontRender); + m_ComboDrive.SetFontEx(m_FontFace, 16, 16, m_ZoomRatio, m_FontRatio, m_ComboText, m_ComboTextSelected, FW_NORMAL, m_FontRender); + m_ComboUnit.SetFontEx(m_FontFace, 16, 16, m_ZoomRatio, m_FontRatio, m_ComboText, m_ComboTextSelected, FW_NORMAL, m_FontRender); + + m_ButtonTest0.SetMargin(m_MarginButtonTop, m_MarginButtonLeft, m_MarginButtonBottom, m_MarginButtonRight, m_ZoomRatio); + m_ButtonTest1.SetMargin(m_MarginButtonTop, m_MarginButtonLeft, m_MarginButtonBottom, m_MarginButtonRight, m_ZoomRatio); + m_ButtonTest2.SetMargin(m_MarginButtonTop, m_MarginButtonLeft, m_MarginButtonBottom, m_MarginButtonRight, m_ZoomRatio); + m_ButtonTest3.SetMargin(m_MarginButtonTop, m_MarginButtonLeft, m_MarginButtonBottom, m_MarginButtonRight, m_ZoomRatio); + + m_ComboCount.SetItemHeightAll(24, m_ZoomRatio, m_FontRatio); + m_ComboSize.SetItemHeightAll(24, m_ZoomRatio, m_FontRatio); + m_ComboDrive.SetItemHeightAll(24, m_ZoomRatio, m_FontRatio); + m_ComboUnit.SetItemHeightAll(24, m_ZoomRatio, m_FontRatio); + +#ifdef MIX_MODE + if(m_MixMode) + { + m_ComboMix.SetFontEx(m_FontFace, 16, 16, m_ZoomRatio, m_FontRatio, m_ComboText, m_ComboTextSelected, FW_NORMAL, m_FontRender); + m_ComboMix.SetItemHeightAll(24, m_ZoomRatio, m_FontRatio); + + m_MixUnit.SetFontEx(m_FontFace, 16, 16, m_ZoomRatio, m_FontRatio, m_LabelText, FW_BOLD, m_FontRender); + + m_TestMix0.SetFontEx(m_FontFace, 35, 35, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + m_TestMix1.SetFontEx(m_FontFace, 35, 35, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + m_TestMix2.SetFontEx(m_FontFace, 35, 35, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + m_TestMix3.SetFontEx(m_FontFace, 35, 35, m_ZoomRatio, m_FontRatio, m_MeterText, FW_BOLD, m_FontRender); + } +#endif + +#endif + +} + +void CDiskMarkDlg::UpdateQueuesThreads() +{ + CString cstr; + + int type[9] = { 0, 0, 1, 1, 0, 1, 0, 1, 0 }; + int size[9] = { 1024, 1024, 4, 4, 1024, 4, 1024, 4, 1024 }; + int queues[9] = { 8, 1, 32, 1, 8, 32, 1, 1, 8 }; + int threads[9] ={ 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + int measureTimes[13] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 60 }; + int intervalTimes[10] = { 0, 1, 3, 5, 10, 30, 60, 180, 300, 600 }; + + for (int i = 0; i < 9; i++) + { + cstr.Format(L"BenchType%d", i); + m_BenchType[i] = GetPrivateProfileInt(L"Setting", cstr, type[i], m_Ini); + if (m_BenchType[i] < 0 || m_BenchSize[i] > 1) { m_BenchSize[i] = type[i]; } + + cstr.Format(L"BenchSize%d", i); + m_BenchSize[i] = GetPrivateProfileInt(L"Setting", cstr, size[i], m_Ini); + if (m_BenchSize[i] <= 0 || m_BenchSize[i] > 8192) { m_BenchSize[i] = size[i]; } + + cstr.Format(L"BenchQueues%d", i); + m_BenchQueues[i] = GetPrivateProfileInt(L"Setting", cstr,queues[i], m_Ini); + if (m_BenchQueues[i] <= 0 || m_BenchQueues[i] > MAX_QUEUES) { m_BenchQueues[i] = queues[i]; } + + cstr.Format(L"BenchThreads%d", i); + m_BenchThreads[i] = GetPrivateProfileInt(L"Setting", cstr, threads[i], m_Ini); + if (m_BenchThreads[i] <= 0 || m_BenchThreads[i] > MAX_THREADS) { m_BenchThreads[i] = threads[i]; } + } + + m_TestData = GetPrivateProfileInt(L"Setting", L"TestData", TEST_DATA_RANDOM, m_Ini); + if (m_TestData < 0 || m_TestData > 1) + { + m_TestData = TEST_DATA_RANDOM; + } + SetWindowTitle(L""); + + BOOL bMeasureflag = FALSE; + m_MeasureTime = GetPrivateProfileInt(L"Setting", L"MeasureTime", 5, m_Ini); + for (int i = 0; i < 13; i++) + { + if (m_MeasureTime == measureTimes[i]) + { + bMeasureflag = TRUE; + } + } + if (! bMeasureflag) + { + m_MeasureTime = 5; + } + + BOOL bIntervalFlag = FALSE; + m_IntervalTime = GetPrivateProfileInt(L"Setting", L"IntervalTime", 5, m_Ini); + for (int i = 0; i < 10; i++) + { + if (m_IntervalTime == intervalTimes[i]) + { + bIntervalFlag = TRUE; + } + } + if (! bIntervalFlag) + { + m_IntervalTime = 5; + } + + CheckRadioPresetMode(); +} + +void CDiskMarkDlg::SettingsQueuesThreads(int type) +{ + CString key, value; + CString cstr; + + switch (type) + { + case 0:// Default + { + int type[9] = { 0, 0, 1, 1, 0, 1, 0, 1, 0 }; + int size[9] = { 1024, 1024, 4, 4, 1024, 4, 1024, 4, 1024 }; + int queues[9] = { 8, 1, 32, 1, 8, 32, 1, 1, 8 }; + int threads[9] ={ 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + + for (int i = 0; i < 9; i++) + { + key.Format(L"BenchType%d", i); value.Format(L"%d", type[i]); + WritePrivateProfileString(L"Setting", key, value, m_Ini); + key.Format(L"BenchSize%d", i); value.Format(L"%d", size[i]); + WritePrivateProfileString(L"Setting", key, value, m_Ini); + key.Format(L"BenchQueues%d", i); value.Format(L"%d",queues[i]); + WritePrivateProfileString(L"Setting", key, value, m_Ini); + key.Format(L"BenchThreads%d", i); value.Format(L"%d", threads[i]); + WritePrivateProfileString(L"Setting", key, value, m_Ini); + } + WritePrivateProfileString(L"Setting", L"Affinity", L"1", m_Ini); + } + m_MeasureTime = 5; cstr.Format(L"%d", m_MeasureTime); + WritePrivateProfileString(L"Setting", L"MeasureTime", cstr, m_Ini); + m_IntervalTime = 5; cstr.Format(L"%d", m_IntervalTime); + WritePrivateProfileString(L"Setting", L"IntervalTime", cstr, m_Ini); + UpdateQueuesThreads(); + ChangeButtonStatus(TRUE); + break; + case 1: // NVMe SSD Ver.8 + { + int type[9] = { 0, 0, 1, 1, 0, 1, 0, 1, 0 }; + int size[9] = { 1024, 128, 4, 4, 1024, 4, 1024, 4, 1024 }; + int queues[9] = { 8, 32, 32, 1, 8, 32, 1, 1, 8 }; + int threads[9] = { 1, 1, 16, 1, 1, 16, 1, 1, 1 }; + for (int i = 0; i < 9; i++) + { + key.Format(L"BenchType%d", i); value.Format(L"%d", type[i]); + WritePrivateProfileString(L"Setting", key, value, m_Ini); + key.Format(L"BenchSize%d", i); value.Format(L"%d", size[i]); + WritePrivateProfileString(L"Setting", key, value, m_Ini); + key.Format(L"BenchQueues%d", i); value.Format(L"%d", queues[i]); + WritePrivateProfileString(L"Setting", key, value, m_Ini); + key.Format(L"BenchThreads%d", i); value.Format(L"%d", threads[i]); + WritePrivateProfileString(L"Setting", key, value, m_Ini); + } + WritePrivateProfileString(L"Setting", L"Affinity", L"1", m_Ini); + } + m_MeasureTime = 5; cstr.Format(L"%d", m_MeasureTime); + WritePrivateProfileString(L"Setting", L"MeasureTime", cstr, m_Ini); + m_IntervalTime = 5; cstr.Format(L"%d", m_IntervalTime); + WritePrivateProfileString(L"Setting", L"IntervalTime", cstr, m_Ini); + UpdateQueuesThreads(); + ChangeButtonStatus(TRUE); + break; + case 2: // Flash Memory + { + int type[9] = { 0, 0, 1, 1, 0, 1, 0, 1, 0 }; + int size[9] = { 1024, 1024, 4, 4, 1024, 4, 1024, 4, 1024 }; + int queues[9] = { 8, 1, 32, 1, 8, 32, 1, 1, 8 }; + int threads[9] ={ 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + for (int i = 0; i < 9; i++) + { + key.Format(L"BenchType%d", i); value.Format(L"%d", type[i]); + WritePrivateProfileString(L"Setting", key, value, m_Ini); + key.Format(L"BenchSize%d", i); value.Format(L"%d", size[i]); + WritePrivateProfileString(L"Setting", key, value, m_Ini); + key.Format(L"BenchQueues%d", i); value.Format(L"%d", queues[i]); + WritePrivateProfileString(L"Setting", key, value, m_Ini); + key.Format(L"BenchThreads%d", i); value.Format(L"%d", threads[i]); + WritePrivateProfileString(L"Setting", key, value, m_Ini); + } + WritePrivateProfileString(L"Setting", L"Affinity", L"1", m_Ini); + } + m_MeasureTime = 1; cstr.Format(L"%d", m_MeasureTime); + WritePrivateProfileString(L"Setting", L"MeasureTime", cstr, m_Ini); + m_IntervalTime = 30; cstr.Format(L"%d", m_IntervalTime); + WritePrivateProfileString(L"Setting", L"IntervalTime", cstr, m_Ini); + UpdateQueuesThreads(); + ChangeButtonStatus(TRUE); + break; + default: + OnSettingsQueuesThreads(); + break; + } +} + +BOOL CDiskMarkDlg::PreTranslateMessage(MSG* pMsg) +{ + if( 0 != ::TranslateAccelerator(m_hWnd, m_hAccelerator, pMsg) ) + { + return TRUE; + } + + return CDialog::PreTranslateMessage(pMsg); +} + +#define STRICT_TYPED_ITEMIDS // Better type safety for IDLists +#include // Typical Shell header file + +INT_PTR CALLBACK BrowseCallbackProc(HWND hWnd, UINT uMsg, LPARAM lParam, LPARAM lpData) +{ + switch (uMsg) + { + case BFFM_INITIALIZED: + if (lpData != NULL) + { + ::SendMessage(hWnd, BFFM_SETSELECTION, TRUE, lpData); + } + break; + } + return 0; +} + +void CDiskMarkDlg::SelectDrive() +{ + CString cstr; + if (m_DiskBenchStatus) + { + return ; + } + + int previousComboDriveIndex = m_IndexTestDrive; + + UpdateData(TRUE); + + if (m_ComboDrive.GetCurSel() == m_MaxIndexTestDrive) + { + BROWSEINFO bi; + ZeroMemory(&bi, sizeof(BROWSEINFO)); + ITEMIDLIST __unaligned *idl; + LPMALLOC g_pMalloc; + TCHAR szTmp[MAX_PATH]; + + HRESULT hResult = SHGetMalloc(&g_pMalloc); + if (hResult == NOERROR) + { + bi.hwndOwner = this->m_hWnd; + bi.lpfn = (BFFCALLBACK)BrowseCallbackProc; + bi.lParam = (LPARAM)m_TestTargetPath.GetString(); + bi.pszDisplayName = szTmp; + bi.lpszTitle = L""; + bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI | BIF_NONEWFOLDERBUTTON; + idl = SHBrowseForFolderW(&bi); + if (idl != NULL) + { + if (SHGetPathFromIDList(idl, szTmp) != FALSE) + { + WritePrivateProfileString(L"Setting", L"TargetPath", szTmp, m_Ini); + m_TestTargetPath = szTmp; + } + g_pMalloc->Free(idl); + m_ComboDrive.SetToolTipText(m_TestTargetPath); + } + else + { + m_IndexTestDrive = previousComboDriveIndex; + m_ComboDrive.SetCurSel(m_IndexTestDrive); + m_ComboDrive.SetToolTipText(i18n(L"Title", L"TEST_DRIVE")); + + } + g_pMalloc->Release(); + } + } + else + { + m_ComboDrive.SetToolTipText(i18n(L"Title", L"TEST_DRIVE")); + } +} + + +LRESULT CDiskMarkDlg::OnUpdateScore(WPARAM wParam, LPARAM lParam) +{ + UpdateScore(); + return 0; +} + +LRESULT CDiskMarkDlg::OnExitBenchmark(WPARAM wParam, LPARAM lParam) +{ + ChangeButtonStatus(TRUE); + EnableMenus(); + + return 0; +} + +void CDiskMarkDlg::OnPaint() +{ + if (IsIconic()) + { + CPaintDC dc(this); // device context for painting + + SendMessage(WM_ICONERASEBKGND, reinterpret_cast(dc.GetSafeHdc()), 0); + + // Center icon in client rectangle + int cxIcon = GetSystemMetrics(SM_CXICON); + int cyIcon = GetSystemMetrics(SM_CYICON); + CRect rect; + GetClientRect(&rect); + int x = (rect.Width() - cxIcon + 1) / 2; + int y = (rect.Height() - cyIcon + 1) / 2; + + // Draw the icon + dc.DrawIcon(x, y, m_hIcon); + } + else + { + CMainDialogFx::OnPaint(); + } +} + +HCURSOR CDiskMarkDlg::OnQueryDragIcon() +{ + return static_cast(m_hIcon); +} + +void CDiskMarkDlg::OnOK() +{ + +} + +void CDiskMarkDlg::OnExit() +{ + OnCancel(); +} + +void CDiskMarkDlg::OnAbout() +{ + m_AboutDlg = new CAboutDlg(this); + m_AboutDlg->Create(CAboutDlg::IDD, m_AboutDlg, ID_ABOUT, this); +} + +void CDiskMarkDlg::OnCancel() +{ + if (m_WinThread != NULL) + { + AfxMessageBox(m_MesStopBenchmark); + return; + } + + UpdateData(TRUE); + CString cstr; + cstr.Format(L"%d", m_IndexTestUnit); + WritePrivateProfileString(L"Setting", L"TestUnit", cstr, m_Ini); + cstr.Format(L"%d", m_IndexTestCount); + WritePrivateProfileString(L"Setting", L"TestCount", cstr, m_Ini); + cstr.Format(L"%d", m_IndexTestSize); + WritePrivateProfileString(L"Setting", L"TestSize", cstr, m_Ini); +#ifdef MIX_MODE + if (m_MixMode) + { + cstr.Format(L"%d", m_IndexTestMix); + WritePrivateProfileString(L"Setting", L"TestMix", cstr, m_Ini); + } +#endif + + if(m_IndexTestDrive != m_MaxIndexTestDrive) + { + cstr.Format(L"%d", m_ValueTestDrive.GetAt(0) - 'A'); + } + else + { + cstr.Format(L"%d", 99); + } + WritePrivateProfileString(L"Setting", L"DriveLetter", cstr, m_Ini); + + CMainDialogFx::OnCancel(); +} + +void CDiskMarkDlg::InitScore() +{ + for (int i = 0; i < 9; i++) + { + m_ReadScore[i] = 0.0; + m_ReadLatency[i] = 0.0; + m_WriteScore[i] = 0.0; + m_WriteLatency[i] = 0.0; +#ifdef MIX_MODE + m_MixScore[i] = 0.0; + m_MixLatency[i] = 0.0; +#endif + } + + UpdateScore(); +} + +void CDiskMarkDlg::UpdateScore() +{ + UpdateData(TRUE); + if (m_Profile == PROFILE_DEMO) + { + SetMeter(&m_TestRead0, m_ReadScore[8], m_ReadLatency[8], m_BenchSize[8] * 1024, m_IndexTestUnit); + SetMeter(&m_TestWrite0, m_WriteScore[8], m_WriteLatency[8], m_BenchSize[8] * 1024, m_IndexTestUnit); + } + else if (m_Profile == PROFILE_PEAK || m_Profile == PROFILE_PEAK_MIX) + { + SetMeter(&m_TestRead0, m_ReadScore[4], m_ReadLatency[4], m_BenchSize[4] * 1024, SCORE_MBS); + SetMeter(&m_TestRead1, m_ReadScore[5], m_ReadLatency[5], m_BenchSize[5] * 1024, SCORE_MBS); + SetMeter(&m_TestRead2, m_ReadScore[5], m_ReadLatency[5], m_BenchSize[5] * 1024, SCORE_IOPS); + SetMeter(&m_TestRead3, m_ReadScore[5], m_ReadLatency[5], m_BenchSize[5] * 1024, SCORE_US); + SetMeter(&m_TestWrite0, m_WriteScore[4], m_WriteLatency[4], m_BenchSize[4] * 1024, SCORE_MBS); + SetMeter(&m_TestWrite1, m_WriteScore[5], m_WriteLatency[5], m_BenchSize[5] * 1024, SCORE_MBS); + SetMeter(&m_TestWrite2, m_WriteScore[5], m_WriteLatency[5], m_BenchSize[5] * 1024, SCORE_IOPS); + SetMeter(&m_TestWrite3, m_WriteScore[5], m_WriteLatency[5], m_BenchSize[5] * 1024, SCORE_US); +#ifdef MIX_MODE + if (m_MixMode) + { + SetMeter(&m_TestMix0, m_MixScore[4], m_MixLatency[4], m_BenchSize[4] * 1024, SCORE_MBS); + SetMeter(&m_TestMix1, m_MixScore[5], m_MixLatency[5], m_BenchSize[5] * 1024, SCORE_MBS); + SetMeter(&m_TestMix2, m_MixScore[5], m_MixLatency[5], m_BenchSize[5] * 1024, SCORE_IOPS); + SetMeter(&m_TestMix3, m_MixScore[5], m_MixLatency[5], m_BenchSize[5] * 1024, SCORE_US); + } +#endif + } + else if (m_Profile == PROFILE_REAL || m_Profile == PROFILE_REAL_MIX) + { + SetMeter(&m_TestRead0, m_ReadScore[6], m_ReadLatency[6], 1024 * 1024, SCORE_MBS); + SetMeter(&m_TestRead1, m_ReadScore[7], m_ReadLatency[7], 4 * 1024, SCORE_MBS); + SetMeter(&m_TestRead2, m_ReadScore[7], m_ReadLatency[7], 4 * 1024, SCORE_IOPS); + SetMeter(&m_TestRead3, m_ReadScore[7], m_ReadLatency[7], 4 * 1024, SCORE_US); + SetMeter(&m_TestWrite0, m_WriteScore[6], m_WriteLatency[6], 1024 * 1024, SCORE_MBS); + SetMeter(&m_TestWrite1, m_WriteScore[7], m_WriteLatency[7], 4 * 1024, SCORE_MBS); + SetMeter(&m_TestWrite2, m_WriteScore[7], m_WriteLatency[7], 4 * 1024, SCORE_IOPS); + SetMeter(&m_TestWrite3, m_WriteScore[7], m_WriteLatency[7], 4 * 1024, SCORE_US); +#ifdef MIX_MODE + if (m_MixMode) + { + SetMeter(&m_TestMix0, m_MixScore[6], m_MixLatency[6], 1024 * 1024, SCORE_MBS); + SetMeter(&m_TestMix1, m_MixScore[7], m_MixLatency[7], 4 * 1024, SCORE_MBS); + SetMeter(&m_TestMix2, m_MixScore[7], m_MixLatency[7], 4 * 1024, SCORE_IOPS); + SetMeter(&m_TestMix3, m_MixScore[7], m_MixLatency[7], 4 * 1024, SCORE_US); + } +#endif + } + else + { + SetMeter(&m_TestRead0, m_ReadScore[0], m_ReadLatency[0], m_BenchSize[0] * 1024, m_IndexTestUnit); + SetMeter(&m_TestRead1, m_ReadScore[1], m_ReadLatency[1], m_BenchSize[1] * 1024, m_IndexTestUnit); + SetMeter(&m_TestRead2, m_ReadScore[2], m_ReadLatency[2], m_BenchSize[2] * 1024, m_IndexTestUnit); + SetMeter(&m_TestRead3, m_ReadScore[3], m_ReadLatency[3], m_BenchSize[3] * 1024, m_IndexTestUnit); + SetMeter(&m_TestWrite0, m_WriteScore[0], m_WriteLatency[0], m_BenchSize[0] * 1024, m_IndexTestUnit); + SetMeter(&m_TestWrite1, m_WriteScore[1], m_WriteLatency[1], m_BenchSize[1] * 1024, m_IndexTestUnit); + SetMeter(&m_TestWrite2, m_WriteScore[2], m_WriteLatency[2], m_BenchSize[2] * 1024, m_IndexTestUnit); + SetMeter(&m_TestWrite3, m_WriteScore[3], m_WriteLatency[3], m_BenchSize[3] * 1024, m_IndexTestUnit); +#ifdef MIX_MODE + if (m_MixMode) + { + SetMeter(&m_TestMix0, m_MixScore[0], m_MixLatency[0], m_BenchSize[0] * 1024, m_IndexTestUnit); + SetMeter(&m_TestMix1, m_MixScore[1], m_MixLatency[1], m_BenchSize[1] * 1024, m_IndexTestUnit); + SetMeter(&m_TestMix2, m_MixScore[2], m_MixLatency[2], m_BenchSize[2] * 1024, m_IndexTestUnit); + SetMeter(&m_TestMix3, m_MixScore[3], m_MixLatency[3], m_BenchSize[3] * 1024, m_IndexTestUnit); + } +#endif + } +} + +void CDiskMarkDlg::SetScoreToolTip(CStaticFx* control, double score, double latency, int blockSize) +{ + CString cstr; + if (blockSize == -1) + { + cstr.Format(L"%.3f MB/s\r\n%.3f GB/s", score, score / 1000); + } + else if (score <= 0.0) + { + cstr.Format(L"%.3f MB/s\r\n%.3f GB/s\r\n%.3f IOPS\r\n%.3f μs", 0.0, 0.0, 0.0, 0.0); + } + else + { + cstr.Format(L"%.3f MB/s\r\n%.3f GB/s\r\n%.3f IOPS\r\n%.3f μs", score, score / 1000, score * 1000 * 1000 / blockSize, latency); + } + control->SetToolTipText(cstr); +} + +void CDiskMarkDlg::OnSequentialPeak() +{ + if (m_WinThread == NULL) + { + UpdateData(TRUE); + + m_ReadScore[4] = 0.0; + m_WriteScore[4] = 0.0; + m_ReadLatency[4] = 0.0; + m_WriteLatency[4] = 0.0; +#ifdef MIX_MODE + m_MixScore[4] = 0.0; + m_MixLatency[4] = 0.0; +#endif + UpdateScore(); + m_DiskBenchStatus = TRUE; + m_WinThread = AfxBeginThread(ExecDiskBench4, (void*)this); + if (m_WinThread == NULL) + { + m_DiskBenchStatus = FALSE; + } + else + { + ChangeButtonStatus(FALSE); + } + DisableMenus(); + } + else + { + Stop(); + } +} + +void CDiskMarkDlg::OnRandomPeak() +{ + if (m_WinThread == NULL) + { + UpdateData(TRUE); + + m_ReadScore[5] = 0.0; + m_WriteScore[5] = 0.0; + m_ReadLatency[5] = 0.0; + m_WriteLatency[5] = 0.0; +#ifdef MIX_MODE + m_MixScore[5] = 0.0; + m_MixLatency[5] = 0.0; +#endif + UpdateScore(); + m_DiskBenchStatus = TRUE; + m_WinThread = AfxBeginThread(ExecDiskBench5, (void*)this); + if (m_WinThread == NULL) + { + m_DiskBenchStatus = FALSE; + } + else + { + ChangeButtonStatus(FALSE); + } + DisableMenus(); + } + else + { + Stop(); + } +} + +void CDiskMarkDlg::OnSequentialReal() +{ + if (m_WinThread == NULL) + { + UpdateData(TRUE); + + m_ReadScore[6] = 0.0; + m_WriteScore[6] = 0.0; + m_ReadLatency[6] = 0.0; + m_WriteLatency[6] = 0.0; +#ifdef MIX_MODE + m_MixScore[6] = 0.0; + m_MixLatency[6] = 0.0; +#endif + UpdateScore(); + m_DiskBenchStatus = TRUE; + m_WinThread = AfxBeginThread(ExecDiskBench6, (void*)this); + if (m_WinThread == NULL) + { + m_DiskBenchStatus = FALSE; + } + else + { + ChangeButtonStatus(FALSE); + } + DisableMenus(); + } + else + { + Stop(); + } +} + +void CDiskMarkDlg::OnRandomReal() +{ + if (m_WinThread == NULL) + { + UpdateData(TRUE); + + m_ReadScore[7] = 0.0; + m_WriteScore[7] = 0.0; + m_ReadLatency[7] = 0.0; + m_WriteLatency[7] = 0.0; +#ifdef MIX_MODE + m_MixScore[7] = 0.0; + m_MixLatency[7] = 0.0; +#endif + UpdateScore(); + m_DiskBenchStatus = TRUE; + m_WinThread = AfxBeginThread(ExecDiskBench7, (void*)this); + if (m_WinThread == NULL) + { + m_DiskBenchStatus = FALSE; + } + else + { + ChangeButtonStatus(FALSE); + } + DisableMenus(); + } + else + { + Stop(); + } +} + +void CDiskMarkDlg::OnTest0() +{ + if (m_Profile == PROFILE_PEAK || m_Profile == PROFILE_PEAK_MIX) + { + OnSequentialPeak(); + return; + } + else if (m_Profile == PROFILE_REAL || m_Profile == PROFILE_REAL_MIX) + { + OnSequentialReal(); + return; + } + + if(m_WinThread == NULL) + { + UpdateData(TRUE); + + m_ReadScore[0] = 0.0; + m_WriteScore[0] = 0.0; + m_ReadLatency[0] = 0.0; + m_WriteLatency[0] = 0.0; +#ifdef MIX_MODE + m_MixScore[0] = 0.0; + m_MixLatency[0] = 0.0; +#endif + UpdateScore(); + m_DiskBenchStatus = TRUE; + m_WinThread = AfxBeginThread(ExecDiskBench0, (void*)this); + if(m_WinThread == NULL) + { + m_DiskBenchStatus = FALSE; + } + else + { + ChangeButtonStatus(FALSE); + } + DisableMenus(); + } + else + { + Stop(); + } +} + +void CDiskMarkDlg::OnTest1() +{ + if (m_Profile == PROFILE_PEAK || m_Profile == PROFILE_PEAK_MIX) + { + OnRandomPeak(); + return; + } + else if (m_Profile == PROFILE_REAL || m_Profile == PROFILE_REAL_MIX) + { + OnRandomReal(); + return; + } + + if (m_WinThread == NULL) + { + UpdateData(TRUE); + + m_ReadScore[1] = 0.0; + m_WriteScore[1] = 0.0; + m_ReadLatency[1] = 0.0; + m_WriteLatency[1] = 0.0; +#ifdef MIX_MODE + m_MixScore[1] = 0.0; + m_MixLatency[1] = 0.0; +#endif + UpdateScore(); + m_DiskBenchStatus = TRUE; + m_WinThread = AfxBeginThread(ExecDiskBench1, (void*)this); + if (m_WinThread == NULL) + { + m_DiskBenchStatus = FALSE; + } + else + { + ChangeButtonStatus(FALSE); + } + DisableMenus(); + } + else + { + Stop(); + } +} + +void CDiskMarkDlg::OnTest2() +{ + if (m_Profile == PROFILE_PEAK || m_Profile == PROFILE_PEAK_MIX) + { + OnRandomPeak(); + return; + } + else if (m_Profile == PROFILE_REAL || m_Profile == PROFILE_REAL_MIX) + { + OnRandomReal(); + return; + } + + if(m_WinThread == NULL) + { + UpdateData(TRUE); + + m_ReadScore[2] = 0.0; + m_WriteScore[2] = 0.0; + m_ReadLatency[2] = 0.0; + m_WriteLatency[2] = 0.0; +#ifdef MIX_MODE + m_MixScore[2] = 0.0; + m_MixLatency[2] = 0.0; +#endif + UpdateScore(); + m_DiskBenchStatus = TRUE; + m_WinThread = AfxBeginThread(ExecDiskBench2, (void*)this); + if(m_WinThread == NULL) + { + m_DiskBenchStatus = FALSE; + } + else + { + ChangeButtonStatus(FALSE); + } + DisableMenus(); + } + else + { + Stop(); + } +} + +void CDiskMarkDlg::OnTest3() +{ + if (m_Profile == PROFILE_PEAK || m_Profile == PROFILE_PEAK_MIX) + { + OnRandomPeak(); + return; + } + else if (m_Profile == PROFILE_REAL || m_Profile == PROFILE_REAL_MIX) + { + OnRandomReal(); + return; + } + + if(m_WinThread == NULL) + { + UpdateData(TRUE); + + m_ReadScore[3] = 0.0; + m_WriteScore[3] = 0.0; + m_ReadLatency[3] = 0.0; + m_WriteLatency[3] = 0.0; +#ifdef MIX_MODE + m_MixScore[3] = 0.0; + m_MixLatency[3] = 0.0; +#endif + UpdateScore(); + m_DiskBenchStatus = TRUE; + m_WinThread = AfxBeginThread(ExecDiskBench3, (void*)this); + if(m_WinThread == NULL) + { + m_DiskBenchStatus = FALSE; + } + else + { + ChangeButtonStatus(FALSE); + } + DisableMenus(); + } + else + { + Stop(); + } +} + +void CDiskMarkDlg::OnAll() +{ + if(m_WinThread == NULL) + { + UpdateData(TRUE); + InitScore(); + m_DiskBenchStatus = TRUE; + if (m_Profile == PROFILE_DEMO) + { + m_WinThread = AfxBeginThread(ExecDiskBenchAllDemo, (void*)this); + } + else if (m_Profile == PROFILE_PEAK || m_Profile == PROFILE_PEAK_MIX) + { + m_WinThread = AfxBeginThread(ExecDiskBenchAllPeak, (void*)this); + } + else if (m_Profile == PROFILE_REAL || m_Profile == PROFILE_REAL_MIX) + { + m_WinThread = AfxBeginThread(ExecDiskBenchAllReal, (void*)this); + } + else + { + m_WinThread = AfxBeginThread(ExecDiskBenchAll, (void*)this); + } + + if(m_WinThread == NULL) + { + m_DiskBenchStatus = FALSE; + } + else + { + ChangeButtonStatus(FALSE); + } + DisableMenus(); + } + else + { + Stop(); + } +} + +void CDiskMarkDlg::Stop() +{ + if(m_DiskBenchStatus) + { + m_DiskBenchStatus = FALSE; + + if (pi.hProcess != NULL) + { + TerminateProcess(pi.hProcess, 0); + } + } + EnableMenus(); +} + +void CDiskMarkDlg::EnableMenus() +{ + CMenu *menu = GetMenu(); + menu->EnableMenuItem(0, MF_BYPOSITION | MF_ENABLED); + menu->EnableMenuItem(1, MF_BYPOSITION | MF_ENABLED); + menu->EnableMenuItem(2, MF_BYPOSITION | MF_ENABLED); + menu->EnableMenuItem(3, MF_BYPOSITION | MF_ENABLED); + menu->EnableMenuItem(4, MF_BYPOSITION | MF_ENABLED); + menu->EnableMenuItem(5, MF_BYPOSITION | MF_ENABLED); + SetMenu(menu); +} + +void CDiskMarkDlg::DisableMenus() +{ + CMenu *menu = GetMenu(); + menu->EnableMenuItem(0, MF_BYPOSITION | MF_GRAYED); + menu->EnableMenuItem(1, MF_BYPOSITION | MF_GRAYED); + menu->EnableMenuItem(2, MF_BYPOSITION | MF_GRAYED); + menu->EnableMenuItem(3, MF_BYPOSITION | MF_GRAYED); + menu->EnableMenuItem(4, MF_BYPOSITION | MF_GRAYED); + menu->EnableMenuItem(5, MF_BYPOSITION | MF_GRAYED); + SetMenu(menu); +} + +CString CDiskMarkDlg::GetButtonText(int type, int size, int queues, int threads, int unit) +{ + CString text; + + if (size >= 1024) + { + if (type == BENCH_RND) + { + if (m_Profile == PROFILE_PEAK || m_Profile == PROFILE_PEAK_MIX || m_Profile == PROFILE_REAL || m_Profile == PROFILE_REAL_MIX) + { + if (unit == SCORE_IOPS) + { + text.Format(L"RND%dM\r\n(IOPS)", size / 1024); + } + else if (unit == SCORE_US) + { + text.Format(L"RND%dM\r\n(μs)", size / 1024); + } + else if (unit == SCORE_GBS) + { + text.Format(L"RND%dM\r\nQ%dT%d", size / 1024, queues, threads); + } + else + { + text.Format(L"RND%dM\r\nQ%dT%d", size / 1024, queues, threads); + } + } + else + { + if (unit == SCORE_GBS) + { + text.Format(L"RND%dM\r\nQ%dT%d", size / 1024, queues, threads); + } + else + { + text.Format(L"RND%dM\r\nQ%dT%d", size / 1024, queues, threads); + } + } + } + else + { + if (unit == SCORE_GBS) + { + text.Format(L"SEQ%dM\r\nQ%dT%d", size / 1024, queues, threads); + } + else + { + text.Format(L"SEQ%dM\r\nQ%dT%d", size / 1024, queues, threads); + } + } + } + else + { + if (type == BENCH_RND) + { + if (m_Profile == PROFILE_PEAK || m_Profile == PROFILE_PEAK_MIX || m_Profile == PROFILE_REAL || m_Profile == PROFILE_REAL_MIX) + { + if (unit == SCORE_IOPS) + { + text.Format(L"RND%dK\r\n(IOPS)", size); + } + else if (unit == SCORE_US) + { + text.Format(L"RND%dK\r\n(μs)", size); + } + else if (unit == SCORE_GBS) + { + text.Format(L"RND%dK\r\nQ%dT%d", size, queues, threads); + } + else + { + text.Format(L"RND%dK\r\nQ%dT%d", size, queues, threads); + } + } + else + { + if (unit == SCORE_GBS) + { + text.Format(L"RND%dK\r\nQ%dT%d", size, queues, threads); + } + else + { + text.Format(L"RND%dK\r\nQ%dT%d", size, queues, threads); + } + } + } + else + { + if (unit == SCORE_GBS) + { + text.Format(L"SEQ%dK\r\nQ%dT%d", size, queues, threads); + } + else + { + text.Format(L"SEQ%dK\r\nQ%dT%d", size, queues, threads); + } + } + } + + return text; +} + +CString CDiskMarkDlg::GetButtonToolTipText(int type, int size, int queues, int threads, int unit) +{ + CString text; + + if (size >= 1024) + { + if (type == BENCH_RND) + { + if (unit == SCORE_IOPS) + { + text.Format(L"Random %dMiB\r\nQueues=%d\r\nThreads=%d\r\n(IOPS)", size / 1024, queues, threads); + } + else if (unit == SCORE_US) + { + text.Format(L"Random %dMiB\r\nQueues=%d\r\nThreads=%d\r\n(μs)", size / 1024, queues, threads); + } + else if (unit == SCORE_GBS) + { + text.Format(L"Random %dMiB\r\nQueues=%d\r\nThreads=%d\r\n(GB/s)", size / 1024, queues, threads); + } + else + { + text.Format(L"Random %dMiB\r\nQueues=%d\r\nThreads=%d\r\n(MB/s)", size / 1024, queues, threads); + } + } + else + { + if (unit == SCORE_GBS) + { + text.Format(L"Sequential %dMiB\r\nQueues=%d\r\nThreads=%d\r\n(GB/s)", size / 1024, queues, threads); + } + else + { + text.Format(L"Sequential %dMiB\r\nQueues=%d\r\nThreads=%d\r\n(MB/s)", size / 1024, queues, threads); + } + } + } + else + { + if (type == BENCH_RND) + { + if (unit == SCORE_IOPS) + { + text.Format(L"Random %dKiB\r\nQueues=%d\r\nThreads=%d\r\n(IOPS)", size, queues, threads); + } + else if (unit == SCORE_US) + { + text.Format(L"Random %dKiB\r\nQueues=%d\r\nThreads=%d\r\n(μs)", size, queues, threads); + } + else if (unit == SCORE_GBS) + { + text.Format(L"Random %dKiB\r\nQueues=%d\r\nThreads=%d\r\n(GB/s)", size, queues, threads); + } + else + { + text.Format(L"Random %dKiB\r\nQueues=%d\r\nThreads=%d\r\n(MB/s)", size, queues, threads); + } + } + else + { + if (unit == SCORE_GBS) + { + text.Format(L"Sequential %dKiB\r\nQueues=%d\r\nThreads=%d\r\n(GB/s)", size, queues, threads); + } + else + { + text.Format(L"Sequential %dKiB\r\nQueues=%d\r\nThreads=%d\r\n(MB/s)", size, queues, threads); + } + } + } + + return text; +} + +void CDiskMarkDlg::ChangeButtonStatus(BOOL status) +{ + if(status) + { + CString title; + CString toolTip; + +#ifdef MIX_MODE + m_ComboMix.EnableWindow(TRUE); +#endif + m_ComboCount.EnableWindow(TRUE); + m_ComboSize.EnableWindow(TRUE); + m_ComboDrive.EnableWindow(TRUE); + m_ComboUnit.EnableWindow(TRUE); + + CString cstr; + if (m_MeasureTime == 5) + { + cstr.Format(L"All"); + } + else + { + cstr.Format(L"All\n%dsec", m_MeasureTime); + } + + m_ButtonAll.SetWindowTextW(cstr); + + if (m_Profile == PROFILE_DEMO) + { + m_ButtonTest0.ShowWindow(SW_HIDE); + m_ButtonTest1.ShowWindow(SW_HIDE); + m_ButtonTest2.ShowWindow(SW_HIDE); + m_ButtonTest3.ShowWindow(SW_HIDE); + + CString text, type; + if (m_BenchType[8] == BENCH_SEQ) + { + type = L"SEQ"; + } + else + { + type = L"RND"; + } + if (m_BenchSize[8] > 1000) + { + text.Format(L"%s %dMiB, Q=%d, T=%d", type.GetString(), m_BenchSize[8] / 1024, m_BenchQueues[8], m_BenchThreads[8]); + } + else + { + text.Format(L"%s %dKiB, Q=%d, T=%d", type.GetString(), m_BenchSize[8], m_BenchQueues[8], m_BenchThreads[8]); + } + + m_DemoSetting.SetWindowTextW(text); + } + else if (m_Profile == PROFILE_PEAK || m_Profile == PROFILE_PEAK_MIX) + { + m_ButtonTest0.ShowWindow(SW_SHOW); + m_ButtonTest1.ShowWindow(SW_SHOW); + m_ButtonTest2.ShowWindow(SW_SHOW); + m_ButtonTest3.ShowWindow(SW_SHOW); + + m_ButtonTest0.SetWindowTextW(GetButtonText(BENCH_SEQ, m_BenchSize[4], m_BenchQueues[4], m_BenchThreads[4], SCORE_MBS)); + m_ButtonTest1.SetWindowTextW(GetButtonText(BENCH_RND, m_BenchSize[5], m_BenchQueues[5], m_BenchThreads[5], SCORE_MBS)); + m_ButtonTest2.SetWindowTextW(GetButtonText(BENCH_RND, m_BenchSize[5], m_BenchQueues[5], m_BenchThreads[5], SCORE_IOPS)); + m_ButtonTest3.SetWindowTextW(GetButtonText(BENCH_RND, m_BenchSize[5], m_BenchQueues[5], m_BenchThreads[5], SCORE_US)); + + m_ButtonTest0.SetToolTipText(GetButtonToolTipText(BENCH_SEQ, m_BenchSize[4], m_BenchQueues[4], m_BenchThreads[4], SCORE_MBS)); + m_ButtonTest1.SetToolTipText(GetButtonToolTipText(BENCH_RND, m_BenchSize[5], m_BenchQueues[5], m_BenchThreads[5], SCORE_MBS)); + m_ButtonTest2.SetToolTipText(GetButtonToolTipText(BENCH_RND, m_BenchSize[5], m_BenchQueues[5], m_BenchThreads[5], SCORE_IOPS)); + m_ButtonTest3.SetToolTipText(GetButtonToolTipText(BENCH_RND, m_BenchSize[5], m_BenchQueues[5], m_BenchThreads[5], SCORE_US)); + } + else if (m_Profile == PROFILE_REAL || m_Profile == PROFILE_REAL_MIX) + { + m_ButtonTest0.ShowWindow(SW_SHOW); + m_ButtonTest1.ShowWindow(SW_SHOW); + m_ButtonTest2.ShowWindow(SW_SHOW); + m_ButtonTest3.ShowWindow(SW_SHOW); + + m_ButtonTest0.SetWindowTextW(GetButtonText(BENCH_SEQ, 1024, 1, 1, SCORE_MBS)); + m_ButtonTest1.SetWindowTextW(GetButtonText(BENCH_RND, 4, 1, 1, SCORE_MBS)); + m_ButtonTest2.SetWindowTextW(GetButtonText(BENCH_RND, 4, 1, 1, SCORE_IOPS)); + m_ButtonTest3.SetWindowTextW(GetButtonText(BENCH_RND, 4, 1, 1, SCORE_US)); + + m_ButtonTest0.SetToolTipText(GetButtonToolTipText(BENCH_SEQ, 1024, 1, 1, SCORE_MBS)); + m_ButtonTest1.SetToolTipText(GetButtonToolTipText(BENCH_RND, 4, 1, 1, SCORE_MBS)); + m_ButtonTest2.SetToolTipText(GetButtonToolTipText(BENCH_RND, 4, 1, 1, SCORE_IOPS)); + m_ButtonTest3.SetToolTipText(GetButtonToolTipText(BENCH_RND, 4, 1, 1, SCORE_US)); + } + else + { + m_ButtonTest0.ShowWindow(SW_SHOW); + m_ButtonTest1.ShowWindow(SW_SHOW); + m_ButtonTest2.ShowWindow(SW_SHOW); + m_ButtonTest3.ShowWindow(SW_SHOW); + + m_ButtonTest0.SetWindowTextW(GetButtonText(m_BenchType[0], m_BenchSize[0], m_BenchQueues[0], m_BenchThreads[0], m_IndexTestUnit)); + m_ButtonTest1.SetWindowTextW(GetButtonText(m_BenchType[1], m_BenchSize[1], m_BenchQueues[1], m_BenchThreads[1], m_IndexTestUnit)); + m_ButtonTest2.SetWindowTextW(GetButtonText(m_BenchType[2], m_BenchSize[2], m_BenchQueues[2], m_BenchThreads[2], m_IndexTestUnit)); + m_ButtonTest3.SetWindowTextW(GetButtonText(m_BenchType[3], m_BenchSize[3], m_BenchQueues[3], m_BenchThreads[3], m_IndexTestUnit)); + + m_ButtonTest0.SetToolTipText(GetButtonToolTipText(m_BenchType[0], m_BenchSize[0], m_BenchQueues[0], m_BenchThreads[0], m_IndexTestUnit)); + m_ButtonTest1.SetToolTipText(GetButtonToolTipText(m_BenchType[1], m_BenchSize[1], m_BenchQueues[1], m_BenchThreads[1], m_IndexTestUnit)); + m_ButtonTest2.SetToolTipText(GetButtonToolTipText(m_BenchType[2], m_BenchSize[2], m_BenchQueues[2], m_BenchThreads[2], m_IndexTestUnit)); + m_ButtonTest3.SetToolTipText(GetButtonToolTipText(m_BenchType[3], m_BenchSize[3], m_BenchQueues[3], m_BenchThreads[3], m_IndexTestUnit)); + } + } + else + { +#ifdef MIX_MODE + m_ComboMix.EnableWindow(FALSE); +#endif + m_ComboCount.EnableWindow(FALSE); + m_ComboSize.EnableWindow(FALSE); + m_ComboDrive.EnableWindow(FALSE); + m_ComboUnit.EnableWindow(FALSE); + + m_ButtonAll.SetWindowTextW(L"Stop"); + m_ButtonTest0.SetWindowTextW(L"Stop"); + m_ButtonTest1.SetWindowTextW(L"Stop"); + m_ButtonTest2.SetWindowTextW(L"Stop"); + m_ButtonTest3.SetWindowTextW(L"Stop"); + } +} + +LRESULT CDiskMarkDlg::OnUpdateMessage(WPARAM wParam, LPARAM lParam) +{ + CString wstr = L""; + CString lstr = L""; + + if(wParam != NULL) + { + wstr = *((CString*)wParam); + } + + if(lParam != NULL) + { + lstr = *((CString*)lParam); + } + + SetWindowTitle(wstr); + return 0; +} + +void CDiskMarkDlg::SetMeter(CStaticFx* control, double score, double latency, int blockSize, int unit) +{ + CString cstr; + + double meterRatio = 0.0; + + if (unit == SCORE_UNIT::SCORE_US) + { + if (latency > 0.0000000001) + { + meterRatio = 1 - 0.16666666666666 * log10(latency); + } + else + { + meterRatio = 0; + } + } + else + { + if (score > 0.1) + { + meterRatio = 0.16666666666666 * log10(score * 10); + } + else + { + meterRatio = 0; + } + } + + if (meterRatio > 1.0) + { + meterRatio = 1.0; + } + + if (unit == SCORE_UNIT::SCORE_IOPS) + { + double iops = score * 1000 * 1000 / blockSize; + if (m_Profile == PROFILE_DEMO) + { + if (iops >= 100000.0) + { + cstr.Format(L"%dk", (int)iops / 1000); + } + else + { + cstr.Format(L"%d", (int)iops); + } + } + else + { + if (iops >= 1000000.0) + { + cstr.Format(L"%d", (int)iops); + } + else + { + cstr.Format(L"%.2f", iops); + } + } + } + else if (unit == SCORE_UNIT::SCORE_US) + { + if (m_Profile == PROFILE_DEMO) + { + if (score <= 0.0) + { + cstr.Format(L"%.1f", 0.0); + if (*control == m_TestRead0) { m_TestRead0.SetLabelUnit(L"Read", L"μs"); } + if (*control == m_TestWrite0) { m_TestWrite0.SetLabelUnit(L"Write", L"μs"); } + } + else if (latency >= 1000000.0) + { + cstr.Format(L"%d", (int)latency / 1000); + if (*control == m_TestRead0) { m_TestRead0.SetLabelUnit(L"Read", L"ms"); } + if (*control == m_TestWrite0) { m_TestWrite0.SetLabelUnit(L"Write", L"ms"); } + } + else if (latency >= 1000.0) + { + cstr.Format(L"%d", (int)latency); + if (*control == m_TestRead0) { m_TestRead0.SetLabelUnit(L"Read", L"μs"); } + if (*control == m_TestWrite0) { m_TestWrite0.SetLabelUnit(L"Write", L"μs"); } + } + else + { + cstr.Format(L"%.1f", latency); + if (*control == m_TestRead0) { m_TestRead0.SetLabelUnit(L"Read", L"μs"); } + if (*control == m_TestWrite0) { m_TestWrite0.SetLabelUnit(L"Write", L"μs"); } + } + } + else + { + if (score <= 0.0) + { + cstr.Format(L"%.2f", 0.0); + } + else if (latency >= 1000000.0) + { + cstr.Format(L"%d", (int)latency); + } + else + { + cstr.Format(L"%.2f", latency); + } + } + } + else if (unit == SCORE_UNIT::SCORE_GBS) + { + if (m_Profile == PROFILE_DEMO) + { + cstr.Format(L"%.1f", score / 1000.0); + } + else + { + cstr.Format(L"%.3f", score / 1000.0); + } + } + else + { + if (m_Profile == PROFILE_DEMO) + { + if (score >= 1000.0) + { + cstr.Format(L"%d", (int)score); + } + else + { + cstr.Format(L"%.1f", score); + } + } + else + { + if (score >= 1000000.0) + { + cstr.Format(L"%d", (int)score); + } + else + { + cstr.Format(L"%.2f", score); + } + } + } + + UpdateData(FALSE); + if (m_Profile == PROFILE_DEMO) + { + control->SetMeter(FALSE, meterRatio); + } + else + { + control->SetMeter(TRUE, meterRatio); + } + control->SetWindowTextW(cstr); + + SetScoreToolTip(control, score, latency, blockSize); +} + +void CDiskMarkDlg::InitDrive() +{ + while (m_ComboDrive.GetCount()) + { + m_ComboDrive.DeleteString(0); + } + + CString cstr; + CString select; + + // list up drive + TCHAR szDrives[256] = {0}; + LPTSTR pDrive = szDrives; + TCHAR rootPath[4] = {0}; + TCHAR fileSystem[32] = {0}; + int count = 0; + GetLogicalDriveStrings(255, szDrives); + + m_IndexTestDrive = 0; + m_TestDriveLetter = GetPrivateProfileInt(L"Setting", L"DriveLetter", 2, m_Ini); // Default "C:\" + + while( pDrive[0] != L'\0' ) + { + ULARGE_INTEGER freeBytesAvailableToCaller = {0}; + ULARGE_INTEGER totalNumberOfBytes = {0}; + ULARGE_INTEGER totalNumberOfFreeBytes = {0}; + + // _tcsupr_s(pDrive, sizeof(TCHAR) * 4); + int result = GetDriveType(pDrive); + + int forward = (int)_tcslen( pDrive ); + + if(result == DRIVE_FIXED || result == DRIVE_REMOTE || result == DRIVE_REMOVABLE || result == DRIVE_RAMDISK) + { + pDrive[1] = L'\0'; + cstr.Format(L"%C: ", pDrive[0]); + freeBytesAvailableToCaller.QuadPart = 0; + totalNumberOfBytes.QuadPart = 0; + totalNumberOfFreeBytes.QuadPart = 0; + if(GetDiskFreeSpaceEx(cstr, &freeBytesAvailableToCaller, &totalNumberOfBytes, &totalNumberOfFreeBytes) != 0) + { + select += cstr; + if(totalNumberOfBytes.QuadPart < ((ULONGLONG)8 * 1024 * 1024 * 1024)) // < 8 GB + { + cstr.Format(L"%s: %.0f%% (%.0f/%.0fMiB)", pDrive, + (double)(totalNumberOfBytes.QuadPart - totalNumberOfFreeBytes.QuadPart) / (double)totalNumberOfBytes.QuadPart * 100, + (totalNumberOfBytes.QuadPart - totalNumberOfFreeBytes.QuadPart) / 1024 / 1024.0, + totalNumberOfBytes.QuadPart / 1024 / 1024.0); + } + else + { + cstr.Format(L"%s: %.0f%% (%.0f/%.0fGiB)", pDrive, + (double)(totalNumberOfBytes.QuadPart - totalNumberOfFreeBytes.QuadPart) / (double)totalNumberOfBytes.QuadPart * 100, + (totalNumberOfBytes.QuadPart - totalNumberOfFreeBytes.QuadPart) / 1024 / 1024 / 1024.0, + totalNumberOfBytes.QuadPart / 1024 / 1024 / 1024.0); + } + select += cstr; + + if(m_TestDriveLetter == pDrive[0] - 'A') + { + m_IndexTestDrive = count; + } + count++; + + m_ComboDrive.AddString(cstr); + } + } + pDrive += forward + 1; + } + + m_ComboDrive.AddString(i18n(L"Menu", L"SELECT_FOLDER")); + + TCHAR str[256]; + GetPrivateProfileString(L"Setting", L"TargetPath", L"", str, 256, m_Ini); + m_TestTargetPath = str; + + if (m_TestDriveLetter == 99) + { + m_IndexTestDrive = count; + } + m_MaxIndexTestDrive = count; + + UpdateDriveToolTip(); + + UpdateData(FALSE); +} + +void CDiskMarkDlg::UpdateDriveToolTip() +{ + m_ComboDrive.SetCurSel(m_IndexTestDrive); + if (m_TestDriveLetter == 99 && !m_TestTargetPath.IsEmpty()) + { + m_ComboDrive.SetToolTipText(m_TestTargetPath); + } + else + { + m_ComboDrive.SetToolTipText(i18n(L"Title", L"TEST_DRIVE")); + } +} + +void CDiskMarkDlg::ChangeLang(CString LangName) +{ + m_CurrentLangPath.Format(L"%s\\%s.lang", (LPTSTR)m_LangDir.GetString(), (LPTSTR)LangName.GetString()); + + CString cstr; + CMenu *menu = GetMenu(); + CMenu subMenu; + + cstr = i18n(L"Menu", L"FILE"); + menu->ModifyMenu(0, MF_BYPOSITION | MF_STRING, 0, cstr); + cstr = i18n(L"Menu", L"SETTINGS"); + menu->ModifyMenu(1, MF_BYPOSITION | MF_STRING, 1, cstr); + cstr = i18n(L"Menu", L"PROFILE"); + menu->ModifyMenu(2, MF_BYPOSITION | MF_STRING, 2, cstr); + cstr = i18n(L"Menu", L"THEME"); + menu->ModifyMenu(3, MF_BYPOSITION | MF_STRING, 3, cstr); + cstr = i18n(L"Menu", L"HELP"); + menu->ModifyMenu(4, MF_BYPOSITION | MF_STRING, 4, cstr); + cstr = i18n(L"Menu", L"LANGUAGE"); + if(cstr.Find(L"Language") >= 0) + { + cstr = L"&Language"; + menu->ModifyMenu(5, MF_BYPOSITION | MF_STRING, 5, cstr); + } + else + { + menu->ModifyMenu(5, MF_BYPOSITION | MF_STRING, 5, cstr + L"(&Language)"); + } + + cstr = i18n(L"Menu", L"FILE_EXIT") + L"\tAlt + F4"; + menu->ModifyMenu(ID_EXIT, MF_STRING, ID_EXIT, cstr); + cstr = i18n(L"Menu", L"SAVE_TEXT") + L"\tCtrl + T"; + menu->ModifyMenu(ID_SAVE_TEXT, MF_STRING, ID_SAVE_TEXT, cstr); + cstr = i18n(L"Menu", L"SAVE_IMAGE") + L"\tCtrl + S"; + menu->ModifyMenu(ID_SAVE_IMAGE, MF_STRING, ID_SAVE_IMAGE, cstr); + + cstr = i18n(L"Menu", L"EDIT_COPY") + L"\tCtrl + Shift + C"; + menu->ModifyMenu(ID_COPY, MF_STRING, ID_COPY, cstr); + + subMenu.Attach(menu->GetSubMenu(1)->GetSafeHmenu()); + cstr = i18n(L"Menu", L"TEST_DATA"); + subMenu.ModifyMenu(0, MF_BYPOSITION, 0, cstr); + subMenu.Detach(); + + cstr = i18n(L"Menu", L"DEFAULT_RANDOM"); + menu->ModifyMenu(ID_MODE_DEFAULT, MF_STRING, ID_MODE_DEFAULT, cstr); + cstr = i18n(L"Menu", L"ALL_ZERO"); + menu->ModifyMenu(ID_MODE_ALL0X00, MF_STRING, ID_MODE_ALL0X00, cstr); + + if (m_TestData == TEST_DATA_ALL0X00) + { + OnModeAll0x00(); + } + else + { + OnModeDefault(); + } + + cstr = i18n(L"Dialog", L"DEFAULT"); + menu->ModifyMenu(ID_SETTING_DEFAULT, MF_STRING, ID_SETTING_DEFAULT, cstr); + + CheckRadioPresetMode(); + + cstr = i18n(L"Menu", L"SETTINGS") + L"\tCtrl + Q"; + menu->ModifyMenu(ID_SETTINGS_QUEUESTHREADS, MF_STRING, ID_SETTINGS_QUEUESTHREADS, cstr); + + cstr = i18n(L"Menu", L"PROFILE_DEFAULT"); + menu->ModifyMenu(ID_PROFILE_DEFAULT, MF_STRING, ID_PROFILE_DEFAULT, cstr); + cstr = i18n(L"Menu", L"PROFILE_PEAK"); + menu->ModifyMenu(ID_PROFILE_PEAK, MF_STRING, ID_PROFILE_PEAK, cstr); + cstr = i18n(L"Menu", L"PROFILE_REAL"); + menu->ModifyMenu(ID_PROFILE_REAL, MF_STRING, ID_PROFILE_REAL, cstr); + cstr = i18n(L"Menu", L"PROFILE_DEMO"); + menu->ModifyMenu(ID_PROFILE_DEMO, MF_STRING, ID_PROFILE_DEMO, cstr); + +#ifdef MIX_MODE + cstr = i18n(L"Menu", L"PROFILE_DEFAULT") + L" [+Mix]"; + menu->ModifyMenu(ID_PROFILE_DEFAULT_MIX, MF_STRING, ID_PROFILE_DEFAULT_MIX, cstr); + cstr = i18n(L"Menu", L"PROFILE_PEAK") + L" [+Mix]"; + menu->ModifyMenu(ID_PROFILE_PEAK_MIX, MF_STRING, ID_PROFILE_PEAK_MIX, cstr); + cstr = i18n(L"Menu", L"PROFILE_REAL") + L" [+Mix]"; + menu->ModifyMenu(ID_PROFILE_REAL_MIX, MF_STRING, ID_PROFILE_REAL_MIX, cstr); +#endif + + cstr = i18n(L"Menu", L"HELP") + L" [Web]" + L"\tF1"; + menu->ModifyMenu(ID_HELP, MF_STRING, ID_HELP, cstr); + cstr = i18n(L"Menu", L"HELP_ABOUT"); + menu->ModifyMenu(ID_ABOUT, MF_STRING, ID_ABOUT, cstr); + + // Theme + subMenu.Attach(menu->GetSubMenu(3)->GetSafeHmenu()); + cstr = i18n(L"Menu", L"ZOOM"); + subMenu.ModifyMenu(0, MF_BYPOSITION, 0, cstr); + subMenu.Detach(); + + cstr = i18n(L"Menu", L"AUTO"); + menu->ModifyMenu(ID_ZOOM_AUTO, MF_STRING, ID_ZOOM_AUTO, cstr); + + cstr = i18n(L"Menu", L"FONT_SETTING") + L"\tCtrl + F"; + menu->ModifyMenu(ID_FONT_SETTING, MF_STRING, ID_FONT_SETTING, cstr); + + CheckRadioZoomType(); + + switch (m_Profile) + { + case PROFILE_DEFAULT: + ProfileDefault(); + break; + case PROFILE_PEAK: + ProfilePeak(); + break; + case PROFILE_REAL: + ProfileReal(); + break; + case PROFILE_DEMO: + ProfileDemo(); + break; +#ifdef MIX_MODE + case PROFILE_DEFAULT_MIX: + ProfileDefaultMix(); + break; + case PROFILE_PEAK_MIX: + ProfilePeakMix(); + break; + case PROFILE_REAL_MIX: + ProfileRealMix(); + break; +#endif + default: + ProfileDefault(); + break; + } + + switch (m_Benchmark) + { + case BENCHMARK_READ_WRITE: + BenchmarkReadWrite(); + break; + case BENCHMARK_READ: + BenchmarkReadOnly(); + break; + case BENCHMARK_WRITE: + BenchmarkWriteOnly(); + break; + default: + BenchmarkReadWrite(); + break; + } + + SetMenu(menu); + + m_MesStopBenchmark = i18n(L"Message", L"STOP_BENCHMARK"); + m_MesDiskCapacityError = i18n(L"Message", L"DISK_CAPACITY_ERROR"); + m_MesDiskCreateFileError = i18n(L"Message", L"DISK_CREATE_FILE_ERROR"); + m_MesDiskWriteError = i18n(L"Message", L"DISK_WRITE_ERROR"); + m_MesDiskReadError = i18n(L"Message", L"DISK_READ_ERROR"); + m_MesDiskSpdNotFound = i18n(L"Message", L"DISK_SPD_NOT_FOUND"); + + UpdateDriveToolTip(); + + WritePrivateProfileString(L"Setting", L"Language", LangName, m_Ini); +} + +BOOL CDiskMarkDlg::OnCommand(WPARAM wParam, LPARAM lParam) +{ + // Select Theme + if (WM_THEME_ID <= wParam && wParam < WM_THEME_ID + (UINT)m_MenuArrayTheme.GetSize()) + { + CMenu menu; + CMenu subMenu; + menu.Attach(GetMenu()->GetSafeHmenu()); + subMenu.Attach(menu.GetSubMenu(MENU_THEME_INDEX)->GetSafeHmenu()); + + m_CurrentTheme = m_MenuArrayTheme.GetAt(wParam - WM_THEME_ID); + if (m_CurrentTheme.Compare(m_RandomThemeLabel) == 0) + { + m_CurrentTheme = GetRandomTheme(); + m_RandomThemeLabel = L"Random"; + m_RandomThemeName = L" (" + m_CurrentTheme + L")"; + + // ChangeTheme save the theme configuration to profile; so if we are on + // Random, then save Random to profile. + ChangeTheme(m_RandomThemeLabel); + } + else + { + ChangeTheme(m_MenuArrayTheme.GetAt(wParam - WM_THEME_ID)); + m_RandomThemeName = L""; + } + + subMenu.ModifyMenu(WM_THEME_ID, MF_STRING, WM_THEME_ID, m_RandomThemeLabel + m_RandomThemeName); + subMenu.CheckMenuRadioItem(WM_THEME_ID, WM_THEME_ID + (UINT)m_MenuArrayTheme.GetSize(), + (UINT)wParam, MF_BYCOMMAND); + subMenu.Detach(); + menu.Detach(); + + if (m_Profile == PROFILE_DEMO && IsFileExist(m_ThemeDir + m_CurrentTheme + L"\\BackgroundDemo-300.png")) + { + m_BackgroundName = L"BackgroundDemo"; + } + else + { + m_BackgroundName = L"Background"; + } + + UpdateThemeInfo(); + UpdateDialogSize(); + + return TRUE; + } + + // Select Language + if(WM_LANGUAGE_ID <= wParam && wParam < WM_LANGUAGE_ID + (UINT)m_MenuArrayLang.GetSize()) + { + CMenu menu; + CMenu subMenu; + CMenu subMenuAN; + CMenu subMenuOZ; + menu.Attach(GetMenu()->GetSafeHmenu()); + subMenu.Attach(menu.GetSubMenu(MENU_LANG_INDEX)->GetSafeHmenu()); + subMenuAN.Attach(subMenu.GetSubMenu(0)->GetSafeHmenu()); + subMenuOZ.Attach(subMenu.GetSubMenu(1)->GetSafeHmenu()); + + m_CurrentLang = m_MenuArrayLang.GetAt(wParam - WM_LANGUAGE_ID); + ChangeLang(m_MenuArrayLang.GetAt(wParam - WM_LANGUAGE_ID)); + subMenuAN.CheckMenuRadioItem(WM_LANGUAGE_ID, WM_LANGUAGE_ID + (UINT)m_MenuArrayLang.GetSize(), + (UINT)wParam, MF_BYCOMMAND); + subMenuOZ.CheckMenuRadioItem(WM_LANGUAGE_ID, WM_LANGUAGE_ID + (UINT)m_MenuArrayLang.GetSize(), + (UINT)wParam, MF_BYCOMMAND); + + subMenuOZ.Detach(); + subMenuAN.Detach(); + subMenu.Detach(); + menu.Detach(); + + UpdateComboTooltip(); + } + + return CMainDialogFx::OnCommand(wParam, lParam); +} + +void CDiskMarkDlg::OnCopy() +{ + SaveText(L""); +} + +void CDiskMarkDlg::OnSaveText() +{ + CString path; + SYSTEMTIME st; + GetLocalTime(&st); + path.Format(L"%s_%04d%02d%02d%02d%02d%02d", PRODUCT_NAME, st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); + + CString filter = L"TEXT (*.txt)|*.txt||"; + CFileDialog save(FALSE, L"txt", path, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER, filter); + + if (save.DoModal() == IDOK) + { + SaveText(save.GetPathName()); + } +} + +void CDiskMarkDlg::OnSaveImage() +{ + SaveImage(); +} + +CString CDiskMarkDlg::GetResultString(int type, double score, double latency, int size, int queues, int threads) +{ + CString result; + double iops = 0.0; + + iops = score * 1000 * 1000 / ((double)size * 1024); + if (latency < 0.0) + { + latency = 0.0; + } + + if (type == BENCH_RND) + { + if (size >= 1024) + { + result.Format(L" RND %4dMiB (Q=%3d, T=%2d): %9.3f MB/s [%9.1f IOPS] <%9.2f us>", size / 1024, queues, threads, score, iops, latency); + } + else + { + result.Format(L" RND %4dKiB (Q=%3d, T=%2d): %9.3f MB/s [%9.1f IOPS] <%9.2f us>", size, queues, threads, score, iops, latency); + } + } + else + { + if (size >= 1024) + { + result.Format(L" SEQ %4dMiB (Q=%3d, T=%2d): %9.3f MB/s [%9.1f IOPS] <%9.2f us>", size / 1024, queues, threads, score, iops, latency); + } + else + { + result.Format(L" SEQ %4dKiB (Q=%3d, T=%2d): %9.3f MB/s [%9.1f IOPS] <%9.2f us>", size, queues, threads, score, iops, latency); + } + } + + return result; +} + +void CDiskMarkDlg::SaveText(CString fileName) +{ + CString cstr, clip; + + UpdateData(TRUE); + + if (m_Profile == PROFILE_DEMO) + { + clip = L"\ +------------------------------------------------------------------------------\r\n\ +%PRODUCT% %VERSION%%EDITION% (C) %COPY_YEAR% hiyohiyo\r\n\ + Crystal Dew World: https://crystalmark.info/\r\n\ +------------------------------------------------------------------------------\r\n\ +* MB/s = 1,000,000 bytes/s [SATA/600 = 600,000,000 bytes/s]\r\n\ +* KB = 1000 bytes, KiB = 1024 bytes\r\n\ +\r\n\ +[Read]\r\n\ +%BenchRead1%\r\n\ +\r\n\ +[Write]\r\n\ +%BenchWrite1%\r\n\ +\r\n\ +Profile: Demo\r\n\ + Test: %TestSize% (x%TestCount%)%Capacity%\r\n\ + Mode:%TestMode%\r\n\ + Time: Measure %MeasureTime% / Interval %IntervalTime% \r\n\ + Date: %Date%\r\n\ + OS: %OS%\r\n\ +%Comment%"; + } + else if (m_Profile == PROFILE_DEFAULT || m_Profile == PROFILE_DEFAULT_MIX) + { + clip = L"\ +------------------------------------------------------------------------------\r\n\ +%PRODUCT% %VERSION%%EDITION% (C) %COPY_YEAR% hiyohiyo\r\n\ + Crystal Dew World: https://crystalmark.info/\r\n\ +------------------------------------------------------------------------------\r\n\ +* MB/s = 1,000,000 bytes/s [SATA/600 = 600,000,000 bytes/s]\r\n\ +* KB = 1000 bytes, KiB = 1024 bytes\r\n\ +\r\n\ +[Read]\r\n\ +%BenchRead1%\r\n\ +%BenchRead2%\r\n\ +%BenchRead3%\r\n\ +%BenchRead4%\r\n\ +\r\n\ +[Write]\r\n\ +%BenchWrite1%\r\n\ +%BenchWrite2%\r\n\ +%BenchWrite3%\r\n\ +%BenchWrite4%\r\n\ +\r\n\ +"; + +#ifdef MIX_MODE + if (m_MixMode) + { + clip += L"\ +[Mix] %MixRatio%\r\n\ +%BenchMix1%\r\n\ +%BenchMix2%\r\n\ +%BenchMix3%\r\n\ +%BenchMix4%\r\n\ +\r\n\ +"; + } +#endif + + clip += L"\ +Profile: Default\r\n\ + Test: %TestSize% (x%TestCount%)%Capacity%\r\n\ + Mode:%TestMode%\r\n\ + Time: Measure %MeasureTime% / Interval %IntervalTime% \r\n\ + Date: %Date%\r\n\ + OS: %OS%\r\n\ +%Comment%"; + } + else + { + clip = L"\ +------------------------------------------------------------------------------\r\n\ +%PRODUCT% %VERSION%%EDITION% (C) %COPY_YEAR% hiyohiyo\r\n\ + Crystal Dew World: https://crystalmark.info/\r\n\ +------------------------------------------------------------------------------\r\n\ +* MB/s = 1,000,000 bytes/s [SATA/600 = 600,000,000 bytes/s]\r\n\ +* KB = 1000 bytes, KiB = 1024 bytes\r\n\ +\r\n\ +[Read]\r\n\ +%SequentialRead1%\r\n\ +%RandomRead1%\r\n\ +\r\n\ +[Write]\r\n\ +%SequentialWrite1%\r\n\ +%RandomWrite1%\r\n\ +\r\n\ +"; + +#ifdef MIX_MODE + if (m_MixMode) + { + clip += L"\ +[Mix] %MixRatio%\r\n\ +%SequentialMix1%\r\n\ +%RandomMix1%\r\n\ +\r\n\ +"; + } +#endif + + if (m_Profile == PROFILE_PEAK || m_Profile == PROFILE_PEAK_MIX) + { + clip += L"\ +Profile: Peak\r\n\ +"; + } + else + { + clip += L"\ +Profile: Real\r\n\ +"; + } + + clip += L"\ + Test: %TestSize% (x%TestCount%)%Capacity%\r\n\ + Mode:%TestMode%\r\n\ + Time: Measure %MeasureTime% / Interval %IntervalTime% \r\n\ + Date: %Date%\r\n\ + OS: %OS%\r\n\ +%Comment%"; + } + + clip.Replace(L"%PRODUCT%", PRODUCT_NAME); + clip.Replace(L"%VERSION%", PRODUCT_VERSION); + + cstr = PRODUCT_EDITION; + if(! cstr.IsEmpty()) + { + clip.Replace(L"%EDITION%", L" " PRODUCT_EDITION); + } + else + { + clip.Replace(L"%EDITION%", PRODUCT_EDITION); + } + clip.Replace(L"%COPY_YEAR%", PRODUCT_COPY_YEAR); + + double iops = 0.0; + double latency = 0.0; + + if (m_Profile == PROFILE_DEMO) + { + clip.Replace(L"%BenchRead1%", GetResultString(m_BenchType[8], m_ReadScore[8], m_ReadLatency[8], m_BenchSize[8], m_BenchQueues[8], m_BenchThreads[8])); + clip.Replace(L"%BenchWrite1%", GetResultString(m_BenchType[8], m_WriteScore[8], m_WriteLatency[8], m_BenchSize[8], m_BenchQueues[8], m_BenchThreads[8])); + + } + else if (m_Profile == PROFILE_PEAK || m_Profile == PROFILE_PEAK_MIX) + { + clip.Replace(L"%SequentialRead1%", GetResultString(BENCH_SEQ, m_ReadScore[4], m_ReadLatency[4], m_BenchSize[4], m_BenchQueues[4], m_BenchThreads[4])); + clip.Replace(L"%SequentialWrite1%", GetResultString(BENCH_SEQ, m_WriteScore[4], m_WriteLatency[4], m_BenchSize[4], m_BenchQueues[4], m_BenchThreads[4])); + clip.Replace(L"%RandomRead1%", GetResultString(BENCH_RND, m_ReadScore[5], m_ReadLatency[5], m_BenchSize[5], m_BenchQueues[5], m_BenchThreads[5])); + clip.Replace(L"%RandomWrite1%", GetResultString(BENCH_RND, m_WriteScore[5], m_WriteLatency[5], m_BenchSize[5], m_BenchQueues[5], m_BenchThreads[5])); + +#ifdef MIX_MODE + if (m_MixMode) + { + clip.Replace(L"%SequentialMix1%", GetResultString(BENCH_SEQ, m_MixScore[4], m_MixLatency[4], m_BenchSize[4], m_BenchQueues[4], m_BenchThreads[4])); + clip.Replace(L"%RandomMix1%", GetResultString(BENCH_RND, m_MixScore[5], m_MixLatency[5], m_BenchSize[5], m_BenchQueues[5], m_BenchThreads[5])); + cstr.Format(L"Read %d%%/Write %d%%", 100 - m_MixRatio, m_MixRatio); + clip.Replace(L"%MixRatio%", cstr); + } +#endif + } + else if (m_Profile == PROFILE_REAL || m_Profile == PROFILE_REAL_MIX) + { + clip.Replace(L"%SequentialRead1%", GetResultString(BENCH_SEQ, m_ReadScore[6], m_ReadLatency[6], 1024, 1, 1)); + clip.Replace(L"%SequentialWrite1%", GetResultString(BENCH_SEQ, m_WriteScore[6], m_WriteLatency[6], 1024, 1, 1)); + clip.Replace(L"%RandomRead1%", GetResultString(BENCH_RND, m_ReadScore[7], m_ReadLatency[7], 4, 1, 1)); + clip.Replace(L"%RandomWrite1%", GetResultString(BENCH_RND, m_WriteScore[7], m_WriteLatency[7], 4, 1, 1)); + +#ifdef MIX_MODE + if (m_MixMode) + { + clip.Replace(L"%SequentialMix1%", GetResultString(BENCH_SEQ, m_MixScore[6], m_MixLatency[6], 1024, 1, 1)); + clip.Replace(L"%RandomMix1%", GetResultString(BENCH_RND, m_MixScore[7], m_MixLatency[7], 4, 1, 1)); + cstr.Format(L"Read %d%%/Write %d%%", 100 - m_MixRatio, m_MixRatio); + clip.Replace(L"%MixRatio%", cstr); + } +#endif + } + + else + { + clip.Replace(L"%BenchRead1%", GetResultString(m_BenchType[0], m_ReadScore[0], m_ReadLatency[0], m_BenchSize[0], m_BenchQueues[0], m_BenchThreads[0])); + clip.Replace(L"%BenchRead2%", GetResultString(m_BenchType[1], m_ReadScore[1], m_ReadLatency[1], m_BenchSize[1], m_BenchQueues[1], m_BenchThreads[1])); + clip.Replace(L"%BenchRead3%", GetResultString(m_BenchType[2], m_ReadScore[2], m_ReadLatency[2], m_BenchSize[2], m_BenchQueues[2], m_BenchThreads[2])); + clip.Replace(L"%BenchRead4%", GetResultString(m_BenchType[3], m_ReadScore[3], m_ReadLatency[3], m_BenchSize[3], m_BenchQueues[3], m_BenchThreads[3])); + + clip.Replace(L"%BenchWrite1%", GetResultString(m_BenchType[0], m_WriteScore[0], m_WriteLatency[0], m_BenchSize[0], m_BenchQueues[0], m_BenchThreads[0])); + clip.Replace(L"%BenchWrite2%", GetResultString(m_BenchType[1], m_WriteScore[1], m_WriteLatency[1], m_BenchSize[1], m_BenchQueues[1], m_BenchThreads[1])); + clip.Replace(L"%BenchWrite3%", GetResultString(m_BenchType[2], m_WriteScore[2], m_WriteLatency[2], m_BenchSize[2], m_BenchQueues[2], m_BenchThreads[2])); + clip.Replace(L"%BenchWrite4%", GetResultString(m_BenchType[3], m_WriteScore[3], m_WriteLatency[3], m_BenchSize[3], m_BenchQueues[3], m_BenchThreads[3])); + +#ifdef MIX_MODE + if (m_MixMode) + { + clip.Replace(L"%BenchMix1%", GetResultString(m_BenchType[0], m_MixScore[0], m_MixLatency[0], m_BenchSize[0], m_BenchQueues[0], m_BenchThreads[0])); + clip.Replace(L"%BenchMix2%", GetResultString(m_BenchType[1], m_MixScore[1], m_MixLatency[1], m_BenchSize[1], m_BenchQueues[1], m_BenchThreads[1])); + clip.Replace(L"%BenchMix3%", GetResultString(m_BenchType[2], m_MixScore[2], m_MixLatency[2], m_BenchSize[2], m_BenchQueues[2], m_BenchThreads[2])); + clip.Replace(L"%BenchMix4%", GetResultString(m_BenchType[3], m_MixScore[3], m_MixLatency[3], m_BenchSize[3], m_BenchQueues[3], m_BenchThreads[3])); + + cstr.Format(L"Read %d%%/Write %d%%", 100 - m_MixRatio, m_MixRatio); + clip.Replace(L"%MixRatio%", cstr); + } +#endif + } + + if (m_ValueTestSize.Find(L"MiB") == -1) + { + cstr.Format(L"%d GiB", _tstoi(m_ValueTestSize)); + } + else + { + cstr.Format(L"%d MiB", _tstoi(m_ValueTestSize)); + } + + clip.Replace(L"%TestSize%", cstr); + cstr.Format(L"%d", _tstoi(m_ValueTestCount)); + clip.Replace(L"%TestCount%", cstr); + + cstr = L""; + if (m_AdminMode){ cstr += L" [Admin]"; } + if (m_TestData) { cstr += L" <0Fill>"; } + clip.Replace(L"%TestMode%", cstr); + + m_Comment.GetWindowText(cstr); + if (cstr.IsEmpty()) + { + clip.Replace(L"%Comment%", L""); + }else + { + clip.Replace(L"%Comment%", L"Comment: " + cstr + L"\r\n"); + } + + cstr.Format(L"%d sec", m_IntervalTime); + clip.Replace(L"%IntervalTime%", cstr); + cstr.Format(L"%d sec", m_MeasureTime); + clip.Replace(L"%MeasureTime%", cstr); + + CString null; + GetOsName(cstr, null, null, null); + clip.Replace(L"%OS%", cstr); + + SYSTEMTIME st; + GetLocalTime(&st); + cstr.Format(L"%04d/%02d/%02d %d:%02d:%02d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); + clip.Replace(L"%Date%", cstr); + + if (m_ValueTestDrive.FindOneOf(L":") != -1) + { + clip.Replace(L"%Capacity%", L" [" + m_ValueTestDrive + L"]"); + } + else + { + clip.Replace(L"%Capacity%", L""); + } + + if (fileName.IsEmpty()) + { + if (OpenClipboard()) + { + HGLOBAL clipbuffer; + TCHAR* buffer; + EmptyClipboard(); + clipbuffer = GlobalAlloc(GMEM_DDESHARE, sizeof(TCHAR) * (clip.GetLength() + 1)); + if (clipbuffer != NULL) + { + buffer = (TCHAR*)GlobalLock(clipbuffer); + if (buffer == NULL) + { + GlobalFree(clipbuffer); + CloseClipboard(); + return; + } + else + { + _tcscpy_s(buffer, clip.GetLength() + 1, LPCTSTR(clip)); + GlobalUnlock(clipbuffer); + SetClipboardData(CF_UNICODETEXT, clipbuffer); + } + } + CloseClipboard(); + } + } + else + { + CT2A utf8(clip, CP_UTF8); + + CFile file; + if (file.Open(fileName, CFile::modeCreate | CFile::modeWrite)) + { + file.Write((char*)utf8, (UINT)strlen(utf8)); + file.Close(); + } + } +} + +void CDiskMarkDlg::OnZoom100() +{ + if (CheckRadioZoomType(ID_ZOOM_100, 100)) + { + UpdateDialogSize(); + } +} + +void CDiskMarkDlg::CheckRadioPresetMode() +{ + if (IsDefaultMode()) + { + CMenu* menu = GetMenu(); + menu->CheckMenuRadioItem(ID_SETTING_DEFAULT, ID_SETTING_FLASH_MEMORY, ID_SETTING_DEFAULT, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); + } + else if (IsNVMe8Mode()) + { + CMenu* menu = GetMenu(); + menu->CheckMenuRadioItem(ID_SETTING_DEFAULT, ID_SETTING_FLASH_MEMORY, ID_SETTING_NVME_8, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); + } + + else if (IsFlashMemoryMode()) + { + CMenu* menu = GetMenu(); + menu->CheckMenuRadioItem(ID_SETTING_DEFAULT, ID_SETTING_FLASH_MEMORY, ID_SETTING_FLASH_MEMORY, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); + } + else + { + CMenu* menu = GetMenu(); + menu->CheckMenuRadioItem(ID_SETTING_DEFAULT, ID_SETTING_FLASH_MEMORY, 0, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); + } +} + +void CDiskMarkDlg::OnZoom125() +{ + if (CheckRadioZoomType(ID_ZOOM_125, 125)) + { + UpdateDialogSize(); + } +} + +void CDiskMarkDlg::OnZoom150() +{ + if (CheckRadioZoomType(ID_ZOOM_150, 150)) + { + UpdateDialogSize(); + } +} + +void CDiskMarkDlg::OnZoom200() +{ + if (CheckRadioZoomType(ID_ZOOM_200, 200)) + { + UpdateDialogSize(); + } +} + +void CDiskMarkDlg::OnZoom250() +{ + if (CheckRadioZoomType(ID_ZOOM_250, 250)) + { + UpdateDialogSize(); + } +} + +void CDiskMarkDlg::OnZoom300() +{ + if (CheckRadioZoomType(ID_ZOOM_300, 300)) + { + UpdateDialogSize(); + } +} + +void CDiskMarkDlg::OnZoomAuto() +{ + if (CheckRadioZoomType(ID_ZOOM_AUTO, 0)) + { + UpdateDialogSize(); + } +} + +BOOL CDiskMarkDlg::CheckRadioZoomType(int id, int value) +{ + if(m_ZoomType == value) + { + return FALSE; + } + + CMenu *menu = GetMenu(); + menu->CheckMenuRadioItem(ID_ZOOM_100, ID_ZOOM_AUTO, id, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); + + m_ZoomType = value; + + CString cstr; + cstr.Format(L"%d", value); + WritePrivateProfileString(L"Setting", L"ZoomType", cstr, m_Ini); + + ChangeZoomType(m_ZoomType); + + return TRUE; +} + +void CDiskMarkDlg::CheckRadioZoomType() +{ + int id = ID_ZOOM_AUTO; + + switch(m_ZoomType) + { + case 100: id = ID_ZOOM_100; break; + case 125: id = ID_ZOOM_125; break; + case 150: id = ID_ZOOM_150; break; + case 200: id = ID_ZOOM_200; break; + case 250: id = ID_ZOOM_250; break; + case 300: id = ID_ZOOM_300; break; + default: id = ID_ZOOM_AUTO;break; + } + + CMenu *menu = GetMenu(); + menu->CheckMenuRadioItem(ID_ZOOM_100, ID_ZOOM_AUTO, id, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); +} + +void CDiskMarkDlg::OnHelp() +{ + if (GetUserDefaultLCID() == 0x0411) // Japanese + { + OpenUrl(URL_HELP_JA); + } + else // Other Language + { + OpenUrl(URL_HELP_EN); + } +} + +void CDiskMarkDlg::OnCrystalDewWorld() +{ + if (GetUserDefaultLCID() == 0x0411) // Japanese + { + OpenUrl(URL_CRYSTAL_DEW_WORLD_JA); + } + else // Other Language + { + OpenUrl(URL_CRYSTAL_DEW_WORLD_EN); + } +} + +#ifdef MIX_MODE + #define ID_PROFILE_MAX ID_PROFILE_REAL_MIX +#else + #define ID_PROFILE_MAX ID_PROFILE_DEMO +#endif + +void CDiskMarkDlg::OnSettingDefault() +{ + CMenu* menu = GetMenu(); + menu->CheckMenuRadioItem(ID_SETTING_DEFAULT, ID_SETTING_FLASH_MEMORY, ID_SETTING_DEFAULT, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); + + SettingsQueuesThreads(0); +} + +void CDiskMarkDlg::OnSettingNVMe8() +{ + CMenu* menu = GetMenu(); + menu->CheckMenuRadioItem(ID_SETTING_DEFAULT, ID_SETTING_FLASH_MEMORY, ID_SETTING_NVME_8, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); + + SettingsQueuesThreads(1); +} + +void CDiskMarkDlg::OnSettingFlashMemory() +{ + CMenu* menu = GetMenu(); + menu->CheckMenuRadioItem(ID_SETTING_DEFAULT, ID_SETTING_FLASH_MEMORY, ID_SETTING_FLASH_MEMORY, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); + + SettingsQueuesThreads(2); +} + +void CDiskMarkDlg::OnModeDefault() +{ + CMenu *menu = GetMenu(); + menu->CheckMenuRadioItem(ID_MODE_DEFAULT, ID_MODE_ALL0X00, ID_MODE_DEFAULT, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); + + m_TestData = TEST_DATA_RANDOM; + WritePrivateProfileString(L"Setting", L"TestData", L"0", m_Ini); + SetWindowTitle(L""); +} + +void CDiskMarkDlg::OnModeAll0x00() +{ + CMenu *menu = GetMenu(); + menu->CheckMenuRadioItem(ID_MODE_DEFAULT, ID_MODE_ALL0X00, ID_MODE_ALL0X00, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); + + m_TestData = TEST_DATA_ALL0X00; + WritePrivateProfileString(L"Setting", L"TestData", L"1", m_Ini); + SetWindowTitle(L""); +} + +void CDiskMarkDlg::OnProfileDefault() +{ + ShowWindow(SW_HIDE); + ProfileDefault(); + UpdateUnitLabel(); + InitScore(); + UpdateDialogSize(); + ChangeButtonStatus(TRUE); + SetWindowTitle(L""); +} + +void CDiskMarkDlg::ProfileDefault() +{ + CMenu* menu = GetMenu(); + menu->CheckMenuRadioItem(ID_PROFILE_DEFAULT, ID_PROFILE_MAX, ID_PROFILE_DEFAULT, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); + + m_Profile = PROFILE_DEFAULT; + m_MixMode = FALSE; + WritePrivateProfileString(L"Setting", L"Profile", L"0", m_Ini); + m_BackgroundName = L"Background"; +} + +void CDiskMarkDlg::OnProfilePeak() +{ + ShowWindow(SW_HIDE); + ProfilePeak(); + UpdateUnitLabel(); + InitScore(); + UpdateDialogSize(); + ChangeButtonStatus(TRUE); + SetWindowTitle(L""); +} + +void CDiskMarkDlg::ProfilePeak() +{ + CMenu* menu = GetMenu(); + menu->CheckMenuRadioItem(ID_PROFILE_DEFAULT, ID_PROFILE_MAX, ID_PROFILE_PEAK, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); + + m_Profile = PROFILE_PEAK; + m_MixMode = FALSE; + WritePrivateProfileString(L"Setting", L"Profile", L"1", m_Ini); + m_BackgroundName = L"Background"; +} + +void CDiskMarkDlg::OnProfileReal() +{ + ShowWindow(SW_HIDE); + ProfileReal(); + UpdateUnitLabel(); + InitScore(); + UpdateDialogSize(); + ChangeButtonStatus(TRUE); + SetWindowTitle(L""); +} + +void CDiskMarkDlg::ProfileReal() +{ + CMenu* menu = GetMenu(); + menu->CheckMenuRadioItem(ID_PROFILE_DEFAULT, ID_PROFILE_MAX, ID_PROFILE_REAL, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); + + m_Profile = PROFILE_REAL; + m_MixMode = FALSE; + WritePrivateProfileString(L"Setting", L"Profile", L"2", m_Ini); + m_BackgroundName = L"Background"; +} + +void CDiskMarkDlg::OnProfileDemo() +{ + ShowWindow(SW_HIDE); + ProfileDemo(); + UpdateUnitLabel(); + InitScore(); + UpdateDialogSize(); + ChangeButtonStatus(TRUE); + SetWindowTitle(L""); +} + +void CDiskMarkDlg::ProfileDemo() +{ + CMenu* menu = GetMenu(); + menu->CheckMenuRadioItem(ID_PROFILE_DEFAULT, ID_PROFILE_MAX, ID_PROFILE_DEMO, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); + + m_Profile = PROFILE_DEMO; + m_MixMode = FALSE; + WritePrivateProfileString(L"Setting", L"Profile", L"3", m_Ini); + + if (IsFileExist(m_ThemeDir + m_CurrentTheme + L"\\BackgroundDemo-300.png")) + { + m_BackgroundName = L"BackgroundDemo"; + } + else + { + m_BackgroundName = L"Background"; + } +} + +#ifdef MIX_MODE +void CDiskMarkDlg::OnProfileDefaultMix() +{ + ProfileDefaultMix(); + UpdateUnitLabel(); + InitScore(); + UpdateDialogSize(); + ChangeButtonStatus(TRUE); + SetWindowTitle(L""); +} + +void CDiskMarkDlg::ProfileDefaultMix() +{ + CMenu* menu = GetMenu(); + menu->CheckMenuRadioItem(ID_PROFILE_DEFAULT, ID_PROFILE_REAL_MIX, ID_PROFILE_DEFAULT_MIX, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); + + m_Profile = PROFILE_DEFAULT_MIX; + m_MixMode = TRUE; + WritePrivateProfileString(L"Setting", L"Profile", L"4", m_Ini); + m_BackgroundName = L"Background"; +} + +void CDiskMarkDlg::OnProfilePeakMix() +{ + ProfilePeakMix(); + ChangeButtonStatus(TRUE); + UpdateUnitLabel(); + InitScore(); + UpdateDialogSize(); + SetWindowTitle(L""); +} + +void CDiskMarkDlg::ProfilePeakMix() +{ + CMenu* menu = GetMenu(); + menu->CheckMenuRadioItem(ID_PROFILE_DEFAULT, ID_PROFILE_REAL_MIX, ID_PROFILE_PEAK_MIX, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); + + m_Profile = PROFILE_PEAK_MIX; + m_MixMode = TRUE; + WritePrivateProfileString(L"Setting", L"Profile", L"5", m_Ini); + m_BackgroundName = L"Background"; +} + +void CDiskMarkDlg::OnProfileRealMix() +{ + ProfileRealMix(); + ChangeButtonStatus(TRUE); + UpdateUnitLabel(); + InitScore(); + UpdateDialogSize(); + SetWindowTitle(L""); +} + +void CDiskMarkDlg::ProfileRealMix() +{ + CMenu* menu = GetMenu(); + menu->CheckMenuRadioItem(ID_PROFILE_DEFAULT, ID_PROFILE_REAL_MIX, ID_PROFILE_REAL_MIX, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); + + m_Profile = PROFILE_REAL_MIX; + m_MixMode = TRUE; + WritePrivateProfileString(L"Setting", L"Profile", L"6", m_Ini); + m_BackgroundName = L"Background"; +} +#endif + +void CDiskMarkDlg::OnBenchmarkReadWrite() +{ + BenchmarkReadWrite(); +} + +void CDiskMarkDlg::BenchmarkReadWrite() +{ + CMenu* menu = GetMenu(); + menu->CheckMenuRadioItem(ID_BENCHMARK_READ_WRITE, ID_BENCHMARK_WRITE_ONLY, ID_BENCHMARK_READ_WRITE, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); + + m_Benchmark = BENCHMARK_READ_WRITE; + WritePrivateProfileString(L"Setting", L"Benchmark", L"3", m_Ini); +} + +void CDiskMarkDlg::OnBenchmarkReadOnly() +{ + BenchmarkReadOnly(); +} + +void CDiskMarkDlg::BenchmarkReadOnly() +{ + CMenu* menu = GetMenu(); + menu->CheckMenuRadioItem(ID_BENCHMARK_READ_WRITE, ID_BENCHMARK_WRITE_ONLY, ID_BENCHMARK_READ_ONLY, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); + + m_Benchmark = BENCHMARK_READ; + WritePrivateProfileString(L"Setting", L"Benchmark", L"1", m_Ini); +} + +void CDiskMarkDlg::OnBenchmarkWriteOnly() +{ + BenchmarkWriteOnly(); +} + +void CDiskMarkDlg::BenchmarkWriteOnly() +{ + CMenu* menu = GetMenu(); + menu->CheckMenuRadioItem(ID_BENCHMARK_READ_WRITE, ID_BENCHMARK_WRITE_ONLY, ID_BENCHMARK_WRITE_ONLY, MF_BYCOMMAND); + SetMenu(menu); + DrawMenuBar(); + + m_Benchmark = BENCHMARK_WRITE; + WritePrivateProfileString(L"Setting", L"Benchmark", L"2", m_Ini); +} + +void CDiskMarkDlg::OnSettingsQueuesThreads() +{ + if (! m_DiskBenchStatus) + { + CSettingsDlg* dlg = new CSettingsDlg(this); + dlg->DoModal(); + UpdateQueuesThreads(); + ChangeButtonStatus(TRUE); + + UpdateData(FALSE); + + delete dlg; + } +} + +void CDiskMarkDlg::OnFontSetting() +{ + CFontSelectionDlg fontSelection(this); + if (fontSelection.DoModal() == IDOK) + { + m_FontFace = fontSelection.GetFontFace(); + m_FontScale = fontSelection.GetFontScale(); + m_FontRatio = m_FontScale / 100.0; + m_FontRender = fontSelection.GetFontRender(); + + CString cstr; + WritePrivateProfileString(L"Setting", L"FontFace", L"\"" + m_FontFace + L"\"", m_Ini); + cstr.Format(L"%d", m_FontScale); + WritePrivateProfileString(L"Setting", L"FontScale", cstr, m_Ini); + cstr.Format(L"%d", m_FontRender); + WritePrivateProfileString(L"Setting", L"FontRender", cstr, m_Ini); + + UpdateDialogSize(); + } +} + +void CDiskMarkDlg::OnCbnSelchangeComboDrive() +{ + SelectDrive(); +} + +void CDiskMarkDlg::OnCbnSelchangeComboUnit() +{ + UpdateScore(); + UpdateUnitLabel(); +} + +#ifdef MIX_MODE +void CDiskMarkDlg::OnCbnSelchangeComboMix() +{ + UpdateData(TRUE); + + m_MixRatio = (9 - m_IndexTestMix) * 10; + + CString cstr; + cstr.Format(L"%d", m_MixRatio); + WritePrivateProfileString(L"Setting", L"TestMix", cstr, m_Ini); +} +#endif + +void CDiskMarkDlg::UpdateUnitLabel() +{ + if (m_Profile == PROFILE_DEMO) + { + if (m_IndexTestUnit == SCORE_UNIT::SCORE_IOPS) + { + m_TestRead0.SetLabelUnit(L"Read", L"IOPS"); + m_TestWrite0.SetLabelUnit(L"Write", L"IOPS"); + } + else if (m_IndexTestUnit == SCORE_UNIT::SCORE_US) + { + if (m_ReadLatency[8] >= 1000000) + { + m_TestRead0.SetLabelUnit(L"Read", L"ms"); + } + else + { + m_TestRead0.SetLabelUnit(L"Read", L"μs"); + } + + if (m_WriteLatency[8] >= 1000000) + { + m_TestWrite0.SetLabelUnit(L"Write", L"ms"); + } + else + { + m_TestWrite0.SetLabelUnit(L"Write", L"μs"); + } + } + else if (m_IndexTestUnit == SCORE_UNIT::SCORE_GBS) + { + m_TestRead0.SetLabelUnit(L"Read", L"GB/s"); + m_TestWrite0.SetLabelUnit(L"Write", L"GB/s"); + } + else + { + m_TestRead0.SetLabelUnit(L"Read", L"MB/s"); + m_TestWrite0.SetLabelUnit(L"Write", L"MB/s"); + } + } + else + { + m_TestRead0.SetLabelUnit(L"", L""); + m_TestWrite0.SetLabelUnit(L"", L""); + } + + m_TestRead0.Invalidate(); + m_TestWrite0.Invalidate(); + + if (m_Profile == PROFILE_REAL || m_Profile == PROFILE_REAL_MIX) + { + m_ReadUnit.SetWindowTextW(L"Read (MB/s)"); + m_WriteUnit.SetWindowTextW(L"Write (MB/s)"); +#ifdef MIX_MODE + m_MixUnit.SetWindowTextW(L"Mix (MB/s)"); +#endif + return; + } + else if (m_Profile == PROFILE_PEAK || m_Profile == PROFILE_PEAK_MIX) + { + m_ReadUnit.SetWindowTextW(L"Read (MB/s)"); + m_WriteUnit.SetWindowTextW(L"Write (MB/s)"); +#ifdef MIX_MODE + m_MixUnit.SetWindowTextW(L"Mix (MB/s)"); +#endif + return; + } + + if (m_IndexTestUnit == SCORE_UNIT::SCORE_IOPS) + { + m_ReadUnit.SetWindowTextW(L"Read (IOPS)"); + m_WriteUnit.SetWindowTextW(L"Write (IOPS)"); +#ifdef MIX_MODE + m_MixUnit.SetWindowTextW(L"Mix (IOPS)"); +#endif + } + else if (m_IndexTestUnit == SCORE_UNIT::SCORE_US) + { + m_ReadUnit.SetWindowTextW(L"Read (μs)"); + m_WriteUnit.SetWindowTextW(L"Write (μs)"); +#ifdef MIX_MODE + m_MixUnit.SetWindowTextW(L"Mix (μs)"); +#endif + } + else if (m_IndexTestUnit == SCORE_UNIT::SCORE_GBS) + { + m_ReadUnit.SetWindowTextW(L"Read (GB/s)"); + m_WriteUnit.SetWindowTextW(L"Write (GB/s)"); +#ifdef MIX_MODE + m_MixUnit.SetWindowTextW(L"Mix (GB/s)"); +#endif + } + else + { + m_ReadUnit.SetWindowTextW(L"Read (MB/s)"); + m_WriteUnit.SetWindowTextW(L"Write (MB/s)"); +#ifdef MIX_MODE + m_MixUnit.SetWindowTextW(L"Mix (MB/s)"); +#endif + } +} + +void CDiskMarkDlg::SetWindowTitle(CString message) +{ + CString title; + + if (!message.IsEmpty()) + { + title.Format(L"%s", message.GetString()); + } + else + { + title.Format(L"%s %s %s", PRODUCT_NAME, PRODUCT_VERSION, PRODUCT_EDITION); + } + + if (m_AdminMode) + { + title += L" [Admin]"; + } + + if (m_TestData == TEST_DATA_ALL0X00) + { + title += L" <0Fill>"; + } + + SetWindowText(L" " + title + L" "); +} + +void CDiskMarkDlg::OnLButtonDown(UINT nFlags, CPoint point) +{ + // Move Focus to Hide Control + GetDlgItem(IDC_HIDE)->SetFocus(); + + CMainDialogFx::OnLButtonDown(nFlags, point); +} \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/DiskMarkDlg.h b/CristalDiskMark/source/CrystalDiskMark/DiskMarkDlg.h new file mode 100644 index 0000000..46d5a1a --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/DiskMarkDlg.h @@ -0,0 +1,292 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once + +#include "AboutDlg.h" +#include "SettingsDlg.h" +#include "FontSelectionDlg.h" + +#include "DialogFx.h" +#include "MainDialogFx.h" +#include "ButtonFx.h" +#include "StaticFx.h" +#include "ComboBoxFx.h" +#include "EditFx.h" +#include "UtilityFx.h" +#include "OsInfoFx.h" +#include "DarkMode.h" + +class CDiskMarkDlg : public CMainDialogFx +{ +public: + CDiskMarkDlg(CWnd* pParent = NULL); + ~CDiskMarkDlg(); + + enum { IDD = IDD_DISKMARK_DIALOG }; + + enum SCORE_UNIT + { + SCORE_MBS = 0, + SCORE_GBS, + SCORE_IOPS, + SCORE_US, + }; + + enum BENCH_TYPE + { + BENCH_SEQ = 0, + BENCH_RND, + }; + + volatile CWinThread* m_WinThread; + volatile BOOL m_DiskBenchStatus; + + void InitScore(); + void UpdateScore(); + + double m_ReadScore[9]; + double m_WriteScore[9]; + double m_ReadLatency[9]; + double m_WriteLatency[9]; + +#ifdef MIX_MODE + double m_MixScore[9]; + double m_MixLatency[9]; +#endif + + void SetMeter(CStaticFx* control, double score, double latency, int blockSize, int unit); + void ChangeLang(CString LangName); + void UpdateDialogSize(); + void ChangeButtonStatus(BOOL status); + void SetScoreToolTip(CStaticFx* cx, double score, double latency, int blockSize); + void UpdateThemeInfo(); + + CString m_ValueTestUnit; + CString m_ValueTestCount; + CString m_ValueTestSize; + CString m_ValueTestDrive; + CString m_TestDriveInfo; + CString m_TestTargetPath; + long m_TestDriveLetter; + + int m_MaxIndexTestDrive; + int m_IndexTestUnit; + int m_IndexTestCount; + int m_IndexTestSize; + int m_IndexTestDrive; + int m_IndexTestMix; + + int m_BenchType[9]; + int m_BenchSize[9]; + int m_BenchQueues[9]; + int m_BenchThreads[9]; + int m_IntervalTime; + int m_MeasureTime; + + int m_TestData; + int m_Profile; + int m_Benchmark; + + int m_MarginButtonTop; + int m_MarginButtonLeft; + int m_MarginButtonBottom; + int m_MarginButtonRight; + int m_MarginMeterTop; + int m_MarginMeterLeft; + int m_MarginMeterBottom; + int m_MarginMeterRight; + int m_MarginCommentTop; + int m_MarginCommentLeft; + int m_MarginCommentBottom; + int m_MarginCommentRight; + int m_MarginDemoTop; + int m_MarginDemoLeft; + int m_MarginDemoBottom; + int m_MarginDemoRight; + + BOOL m_AdminMode; + BOOL m_MixMode; + int m_MixRatio; + + // Message // + CString m_MesDiskCapacityError; + CString m_MesDiskWriteError; + CString m_MesDiskReadError; + CString m_MesStopBenchmark; + CString m_MesDiskCreateFileError; + CString m_MesDiskSpdNotFound; + + void SetWindowTitle(CString message); + +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + + void OnAll(); + void OnTest0(); + void OnTest1(); + void OnTest2(); + void OnTest3(); + void Stop(); + void OnSequentialPeak(); + void OnRandomPeak(); + void OnSequentialReal(); + void OnRandomReal(); + + void SelectDrive(); + CString GetResultString(int type, double score, double latency, int size, int queues, int threads); + CString GetButtonText(int type, int size, int queues, int threads, int unit); + CString GetButtonToolTipText(int type, int size, int queues, int threads, int unit); + + CString m_TitleTestDrive; + CString m_TitleTestCount; + CString m_TitleTestSize; + CString m_TitleTestQSize; + +protected: + HICON m_hIcon; + HICON m_hIconMini; + HACCEL m_hAccelerator; + + int m_SizeX; + int m_SizeY; + + CAboutDlg* m_AboutDlg; + CSettingsDlg* m_SettingsDlg; + + void SetControlFont(); + void InitDrive(); + void UpdateDriveToolTip(); + + BOOL CheckRadioZoomType(int id, int value); + void CheckRadioZoomType(); + void CheckRadioPresetMode(); + void UpdateQueuesThreads(); + + void EnableMenus(); + void DisableMenus(); + + void SaveText(CString fileName); + + void SetLayeredWindow(HWND hWnd, BYTE alpha); + void UpdateComboTooltip(); + + virtual BOOL CheckThemeEdition(CString name); + + BOOL IsDefaultMode(); + BOOL IsNVMe8Mode(); + BOOL IsFlashMemoryMode(); + +#ifdef MIX_MODE + CStaticFx m_TestMix0; + CStaticFx m_TestMix1; + CStaticFx m_TestMix2; + CStaticFx m_TestMix3; + CStaticFx m_MixUnit; + CComboBoxFx m_ComboMix; +#endif + + CButtonFx m_ButtonAll; + CButtonFx m_ButtonTest0; + CButtonFx m_ButtonTest1; + CButtonFx m_ButtonTest2; + CButtonFx m_ButtonTest3; + + CStaticFx m_TestRead0; + CStaticFx m_TestRead1; + CStaticFx m_TestRead2; + CStaticFx m_TestRead3; + + CStaticFx m_TestWrite0; + CStaticFx m_TestWrite1; + CStaticFx m_TestWrite2; + CStaticFx m_TestWrite3; + + CEditFx m_Comment; + + CComboBoxFx m_ComboCount; + CComboBoxFx m_ComboSize; + CComboBoxFx m_ComboDrive; + CComboBoxFx m_ComboUnit; + + CStaticFx m_WriteUnit; + CStaticFx m_ReadUnit; + CStaticFx m_DemoSetting; + + virtual BOOL OnInitDialog(); + virtual void OnOK(); + virtual void OnCancel(); + virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); + virtual BOOL PreTranslateMessage(MSG* pMsg); + + afx_msg void OnPaint(); + afx_msg HCURSOR OnQueryDragIcon(); + afx_msg LRESULT OnUpdateScore(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnUpdateMessage(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnExitBenchmark(WPARAM wParam, LPARAM lParam); + afx_msg void OnZoom100(); + afx_msg void OnZoom125(); + afx_msg void OnZoom150(); + afx_msg void OnZoom200(); + afx_msg void OnZoom250(); + afx_msg void OnZoom300(); + afx_msg void OnZoomAuto(); + + afx_msg void OnExit(); + afx_msg void OnAbout(); + afx_msg void OnFontSetting(); + + LRESULT OnQueryEndSession(WPARAM wParam, LPARAM lParam); + + DECLARE_MESSAGE_MAP() + +public: + afx_msg void OnCopy(); + afx_msg void OnHelp(); + afx_msg void OnCrystalDewWorld(); + afx_msg void OnModeDefault(); + afx_msg void OnModeAll0x00(); + afx_msg void OnSettingDefault(); + afx_msg void OnSettingNVMe8(); + afx_msg void OnSettingFlashMemory(); + + afx_msg void OnProfileDefault(); + afx_msg void OnProfilePeak(); + afx_msg void OnProfileReal(); + afx_msg void OnProfileDemo(); + afx_msg void OnSaveText(); + afx_msg void OnSaveImage(); + afx_msg void OnSettingsQueuesThreads(); + afx_msg void OnCbnSelchangeComboDrive(); + afx_msg void OnCbnSelchangeComboUnit(); + afx_msg void UpdateUnitLabel(); + afx_msg void OnLButtonDown(UINT nFlags, CPoint point); + + void ProfileDefault(); + void ProfilePeak(); + void ProfileReal(); + void ProfileDemo(); + + void SettingsQueuesThreads(int type); + +#ifdef MIX_MODE + afx_msg void OnProfileDefaultMix(); + afx_msg void OnProfilePeakMix(); + afx_msg void OnProfileRealMix(); + void ProfileDefaultMix(); + void ProfilePeakMix(); + void ProfileRealMix(); + afx_msg void OnCbnSelchangeComboMix(); +#endif + + afx_msg void OnBenchmarkReadWrite(); + afx_msg void OnBenchmarkReadOnly(); + afx_msg void OnBenchmarkWriteOnly(); + void BenchmarkReadWrite(); + void BenchmarkReadOnly(); + void BenchmarkWriteOnly(); +}; diff --git a/CristalDiskMark/source/CrystalDiskMark/FontSelectionDlg.cpp b/CristalDiskMark/source/CrystalDiskMark/FontSelectionDlg.cpp new file mode 100644 index 0000000..4172419 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/FontSelectionDlg.cpp @@ -0,0 +1,243 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "DiskMark.h" +#include "DiskMarkDlg.h" +#include "FontSelectionDlg.h" + +int CALLBACK EnumFontFamExProc(ENUMLOGFONTEX *lpelfe, NEWTEXTMETRICEX *lpntme, int FontType, LPARAM lParam); + +IMPLEMENT_DYNAMIC(CFontSelectionDlg, CDialog) + +CFontSelectionDlg::CFontSelectionDlg(CWnd* pParent) + : CDialogFx(CFontSelectionDlg::IDD, pParent) +{ + CMainDialogFx* p = (CMainDialogFx*)pParent; + + m_ZoomType = p->GetZoomType(); + m_FontScale = p->GetFontScale(); + m_FontRatio = 1.0; // p->GetFontRatio(); + m_FontFace = p->GetFontFace(); + m_FontRender = p->GetFontRender(); + m_CurrentLangPath = p->GetCurrentLangPath(); + m_DefaultLangPath = p->GetDefaultLangPath(); + m_ThemeDir = p->GetThemeDir(); + m_CurrentTheme = p->GetCurrentTheme(); + m_DefaultTheme = p->GetDefaultTheme(); + m_Ini = p->GetIniPath(); + + m_BackgroundName = L""; +} + +CFontSelectionDlg::~CFontSelectionDlg() +{ +} + +void CFontSelectionDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + DDX_Control(pDX, IDC_OK, m_CtrlOk); + DDX_Control(pDX, IDC_FONT_FACE, m_LabelFontFace); + DDX_Control(pDX, IDC_FONT_SCALE, m_LabelFontScale); + DDX_Control(pDX, IDC_FONT_RENDER, m_LabelFontRender); + DDX_Control(pDX, IDC_FONT_FACE_COMBO, m_CtrlFontFace); + DDX_Control(pDX, IDC_FONT_SCALE_COMBO, m_CtrlFontScale); + DDX_Control(pDX, IDC_FONT_RENDER_COMBO, m_CtrlFontRender); + DDX_Control(pDX, IDC_SET_DEFAULT, m_CtrlDefault); +} + +BEGIN_MESSAGE_MAP(CFontSelectionDlg, CDialogFx) + ON_BN_CLICKED(IDC_OK, &CFontSelectionDlg::OnOk) + ON_BN_CLICKED(IDC_SET_DEFAULT, &CFontSelectionDlg::OnSetDefault) +END_MESSAGE_MAP() + +BOOL CFontSelectionDlg::OnInitDialog() +{ + CDialogFx::OnInitDialog(); + + SetWindowTitle(i18n(L"WindowTitle", L"FONT_SETTING")); + + SetDefaultFont(m_FontFace); + + CString cstr; + for (int i = 50; i <= 150; i += 10) + { + cstr.Format(L"%d", i); + m_CtrlFontScale.AddString(cstr); + if (m_FontScale == i) { m_CtrlFontScale.SetCurSel(m_CtrlFontScale.GetCount() - 1); } + } + + m_CtrlFontRender.AddString(i18n(L"Dialog", L"ENABLED")); + m_CtrlFontRender.AddString(i18n(L"Dialog", L"DISABLED")); + + if (m_FontRender == CLEARTYPE_NATURAL_QUALITY) + { + m_CtrlFontRender.SetCurSel(0); + } + else + { + m_CtrlFontRender.SetCurSel(1); + } + + m_LabelFontFace.SetWindowTextW(i18n(L"Dialog", L"FONT_FACE")); + m_LabelFontScale.SetWindowTextW(i18n(L"Dialog", L"FONT_SCALE")); + m_LabelFontRender.SetWindowTextW(L"ClearType"); + m_CtrlDefault.SetWindowTextW(i18n(L"Dialog", L"DEFAULT")); + + UpdateDialogSize(); + + return TRUE; +} + +void CFontSelectionDlg::UpdateDialogSize() +{ + CDialogFx::UpdateDialogSize(); + + COLORREF textColor = RGB(0, 0, 0); + COLORREF textSelectedColor = RGB(0, 0, 0); + + ChangeZoomType(m_ZoomType); + SetClientSize(SIZE_X, SIZE_Y, m_ZoomRatio); + + UpdateBackground(FALSE, m_bDarkMode); + + m_LabelFontFace.SetFontEx(m_FontFace, 16, 16, m_ZoomRatio, m_FontRatio, RGB(0, 0, 0), FW_NORMAL, m_FontRender); + m_LabelFontScale.SetFontEx(m_FontFace, 16, 16, m_ZoomRatio, m_FontRatio, RGB(0, 0, 0), FW_NORMAL, m_FontRender); + m_LabelFontRender.SetFontEx(m_FontFace, 16, 16, m_ZoomRatio, m_FontRatio, RGB(0, 0, 0), FW_NORMAL, m_FontRender); + + m_LabelFontFace.InitControl(8, 8, 432, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, FALSE); + m_LabelFontScale.InitControl(8, 80, 208, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, FALSE); + m_LabelFontRender.InitControl(240, 80, 208, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, FALSE); + m_CtrlFontFace.InitControl(20, 32, 440, 360, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_CtrlFontScale.InitControl(20, 104, 208, 360, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_CtrlFontRender.InitControl(252, 104, 208, 360, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + + m_CtrlDefault.InitControl(40, 156, 168, 32, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); + m_CtrlOk.InitControl(272, 156, 168, 32, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); + + m_CtrlFontFace.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_CtrlFontScale.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_CtrlFontRender.SetMargin(0, 4, 0, 0, m_ZoomRatio); + + m_CtrlFontFace.SetFontHeight(20, m_ZoomRatio, m_FontRatio); + m_CtrlFontFace.SetFontEx(m_FontFace, 20, 20, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_CtrlFontFace.SetItemHeightEx(-1, 36, m_ZoomRatio, m_FontRatio); + for (int i = 0; i < m_CtrlFontFace.GetCount(); i++) + { + m_CtrlFontFace.SetItemHeightEx(i, 32, m_ZoomRatio, m_FontRatio); + } + + m_CtrlFontScale.SetFontHeight(20, m_ZoomRatio, m_FontRatio); + m_CtrlFontScale.SetFontEx(m_FontFace, 20, 20, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_CtrlFontScale.SetItemHeightEx(-1, 36, m_ZoomRatio, m_FontRatio); + for (int i = 0; i < m_CtrlFontScale.GetCount(); i++) + { + m_CtrlFontScale.SetItemHeightEx(i, 32, m_ZoomRatio, m_FontRatio); + } + + m_CtrlFontRender.SetFontHeight(20, m_ZoomRatio, m_FontRatio); + m_CtrlFontRender.SetFontEx(m_FontFace, 20, 20, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_CtrlFontRender.SetItemHeightEx(-1, 36, m_ZoomRatio, m_FontRatio); + for (int i = 0; i < m_CtrlFontRender.GetCount(); i++) + { + m_CtrlFontRender.SetItemHeightEx(i, 32, m_ZoomRatio, m_FontRatio); + } + + m_CtrlDefault.SetFontEx(m_FontFace, 16, 16, m_ZoomRatio, m_FontRatio, FW_NORMAL, m_FontRender); + m_CtrlOk.SetFontEx(m_FontFace, 16, 16, m_ZoomRatio, m_FontRatio, FW_NORMAL, m_FontRender); + + m_CtrlDefault.SetHandCursor(); + m_CtrlOk.SetHandCursor(); + + SetDarkModeControl(m_CtrlDefault.GetSafeHwnd(), m_bDarkMode); + SetDarkModeControl(m_CtrlOk.GetSafeHwnd(), m_bDarkMode); + + Invalidate(); +} + +void CFontSelectionDlg::OnOk() +{ + CString cstr; + + m_CtrlFontFace.GetLBText(m_CtrlFontFace.GetCurSel(), m_FontFace); + m_CtrlFontScale.GetLBText(m_CtrlFontScale.GetCurSel(), cstr); + m_FontScale = _wtoi(cstr); + if (m_CtrlFontRender.GetCurSel() == 0) + { + m_FontRender = CLEARTYPE_NATURAL_QUALITY; + } + else + { + m_FontRender = ANTIALIASED_QUALITY; + } + + CDialog::OnOK(); +} + +int CALLBACK EnumFontFamExProc(ENUMLOGFONTEX *lpelfe, NEWTEXTMETRICEX *lpntme, int FontType, LPARAM lParam) +{ + CFontComboBox* pFontComboBox = (CFontComboBox*)lParam; + if(pFontComboBox->FindStringExact(0, (TCHAR*)lpelfe->elfLogFont.lfFaceName) == CB_ERR + && _tcschr((TCHAR*)lpelfe->elfLogFont.lfFaceName, L'@') == NULL + && lpelfe->elfLogFont.lfCharSet != SYMBOL_CHARSET + ) + { + pFontComboBox->AddString((TCHAR*)lpelfe->elfLogFont.lfFaceName); + } + return TRUE; +} + +void CFontSelectionDlg::OnSetDefault() +{ + SetDefaultFont(L""); + m_CtrlFontScale.SetCurSel(5); + m_CtrlFontRender.SetCurSel(0); +} + +void CFontSelectionDlg::SetDefaultFont(CString fontFace) +{ + m_CtrlFontFace.ResetContent(); + + CClientDC dc(this); + LOGFONT logfont; + ZeroMemory(&logfont, sizeof(LOGFONT)); + logfont.lfCharSet = DEFAULT_CHARSET; + + ::EnumFontFamiliesExW(dc.m_hDC, &logfont, (FONTENUMPROC)EnumFontFamExProc, (LPARAM)&m_CtrlFontFace, 0); + + int no = m_CtrlFontFace.FindStringExact(0, fontFace); + if (no >= 0) + { + m_CtrlFontFace.SetCurSel(no); + } + else + { + no = m_CtrlFontFace.FindStringExact(0, DEFAULT_FONT_FACE_1); + if (no >= 0) + { + m_CtrlFontFace.SetCurSel(no); + } + else + { + no = m_CtrlFontFace.FindStringExact(0, DEFAULT_FONT_FACE_2); + if (no >= 0) + { + m_CtrlFontFace.SetCurSel(no); + } + else + { + m_CtrlFontFace.SetCurSel(0); + } + } + } + + for (int i = 0; i < m_CtrlFontFace.GetCount(); i++) + { + m_CtrlFontFace.SetItemHeightEx(i, 32, m_ZoomRatio, m_FontRatio); + } +} \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/FontSelectionDlg.h b/CristalDiskMark/source/CrystalDiskMark/FontSelectionDlg.h new file mode 100644 index 0000000..56dcfaa --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/FontSelectionDlg.h @@ -0,0 +1,48 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once + +#include "afxwin.h" +#include "ButtonFx.h" +#include "ComboBoxFx.h" +#include "FontComboBoxFx.h" + +class CFontSelectionDlg : public CDialogFx +{ + DECLARE_DYNAMIC(CFontSelectionDlg) + + static const int SIZE_X = 480; + static const int SIZE_Y = 204; + enum { IDD = IDD_FONT }; + +public: + CFontSelectionDlg(CWnd* pParent = NULL); + virtual ~CFontSelectionDlg(); + +protected: + virtual void DoDataExchange(CDataExchange* pDX); + virtual BOOL OnInitDialog(); + virtual void UpdateDialogSize(); + + void SetDefaultFont(CString fontFace); + + DECLARE_MESSAGE_MAP() + afx_msg void OnSetDefault(); + afx_msg void OnOk(); + + CStaticFx m_LabelFontFace; + CStaticFx m_LabelFontScale; + CStaticFx m_LabelFontRender; + CButtonFx m_CtrlOk; + CButtonFx m_CtrlDefault; + + CFontComboBox m_CtrlFontFace; + CComboBoxFx m_CtrlFontScale; + CComboBoxFx m_CtrlFontRender; + +}; diff --git a/CristalDiskMark/source/CrystalDiskMark/Library/Resource.h b/CristalDiskMark/source/CrystalDiskMark/Library/Resource.h new file mode 100644 index 0000000..9fa0a4e --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Library/Resource.h @@ -0,0 +1,183 @@ +//{{NO_DEPENDENCIES}} +// +#define IDD_DISKMARK_DIALOG 102 +#define IDR_MENU 129 +#define IDR_MAINFRAME 130 +#define IDD_ABOUT 131 +#define IDD_COMMENT 132 +#define IDR_PNG1 134 +#define IDR_PNG2 135 +#define IDR_ACCELERATOR 136 +#define IDI_ICON1 138 +#define IDI_TRAY_ICON 138 +#define IDD_SETTINGS 139 +#define IDD_FONT 140 + +#define IDC_OK 1001 +#define IDC_BUTTON_ALL 1003 +#define IDC_BUTTON_TEST_0 1004 +#define IDC_BUTTON_TEST_1 1005 +#define IDC_BUTTON_TEST_2 1006 +#define IDC_BUTTON_TEST_3 1007 +#define IDC_TEST_READ_0 1009 +#define IDC_TEST_READ_1 1010 +#define IDC_TEST_READ_2 1011 +#define IDC_TEST_READ_3 1012 +#define IDC_TEST_WRITE_0 1014 +#define IDC_TEST_WRITE_1 1015 +#define IDC_TEST_WRITE_2 1016 +#define IDC_TEST_WRITE_3 1017 +#define IDC_TEST_MIX_0 1019 +#define IDC_TEST_MIX_1 1020 +#define IDC_TEST_MIX_2 1021 +#define IDC_TEST_MIX_3 1022 +#define IDC_COMMENT 1023 +#define IDC_COMMENT_EX 1024 +#define IDC_COMBO_UNIT 1025 +#define IDC_COMBO_COUNT 1026 +#define IDC_COMBO_DRIVE 1027 +#define IDC_COMBO_SIZE 1028 +#define IDC_READ_UNIT 1029 +#define IDC_WRITE_UNIT 1030 +#define IDC_MIX_UNIT 1031 +#define IDC_COMBO_MIX 1032 +#define IDC_DEMO_SETTING 1033 +#define IDC_HIDE 1034 + +#define IDC_LOGO 1100 +#define IDC_PROJECT_SITE_1 1101 +#define IDC_PROJECT_SITE_2 1102 +#define IDC_PROJECT_SITE_3 1103 +#define IDC_PROJECT_SITE_4 1104 +#define IDC_PROJECT_SITE_5 1105 +#define IDC_VERSION 1106 +#define IDC_RELEASE 1107 +#define IDC_COPYRIGHT1 1108 +#define IDC_COPYRIGHT2 1109 +#define IDC_COPYRIGHT3 1110 +#define IDC_LICENSE 1111 +#define IDC_EDITION 1112 + +#define IDC_FONT_FACE_COMBO 1201 +#define IDC_FONT_SCALE_COMBO 1202 +#define IDC_FONT_RENDER_COMBO 1203 +#define IDC_FONT_FACE 1204 +#define IDC_FONT_SCALE 1205 +#define IDC_FONT_RENDER 1206 + +#define IDC_LABEL_DEMO 1301 +#define IDC_COMBO_DATA 1302 +#define IDC_LABEL_DATA 1303 +#define IDC_SET_DEFAULT 1304 +#define IDC_SET_NVME_8 1306 +#define IDC_SET_FLASH_MEMORY 1307 +#define IDC_LABEL_AFFINITY 1308 +#define IDC_COMBO_AFFINITY 1309 +#define IDC_LABEL_PEAK 1310 +#define IDC_LABEL_TYPE 1311 +#define IDC_LABEL_SIZE 1312 +#define IDC_LABEL_QUEUES 1313 +#define IDC_LABEL_THREADS 1314 +#define IDC_LABEL_MEASURE_TIME 1315 +#define IDC_LABEL_INTERVAL_TIME 1316 +#define IDC_COMBO_MEASURE_TIME 1317 +#define IDC_COMBO_INTERVAL_TIME 1318 +#define IDC_LABEL_DEFAULT 1319 + +#define IDC_COMBO_BENCH_TYPE_0 1320 +#define IDC_COMBO_BENCH_TYPE_1 1321 +#define IDC_COMBO_BENCH_TYPE_2 1322 +#define IDC_COMBO_BENCH_TYPE_3 1323 +#define IDC_COMBO_BENCH_TYPE_4 1324 +#define IDC_COMBO_BENCH_TYPE_5 1325 +#define IDC_COMBO_BENCH_TYPE_8 1328 + +#define IDC_COMBO_BENCH_SIZE_0 1330 +#define IDC_COMBO_BENCH_SIZE_1 1331 +#define IDC_COMBO_BENCH_SIZE_2 1332 +#define IDC_COMBO_BENCH_SIZE_3 1333 +#define IDC_COMBO_BENCH_SIZE_4 1334 +#define IDC_COMBO_BENCH_SIZE_5 1335 +#define IDC_COMBO_BENCH_SIZE_8 1338 + +#define IDC_COMBO_BENCH_QUEUE_0 1340 +#define IDC_COMBO_BENCH_QUEUE_1 1341 +#define IDC_COMBO_BENCH_QUEUE_2 1342 +#define IDC_COMBO_BENCH_QUEUE_3 1343 +#define IDC_COMBO_BENCH_QUEUE_4 1344 +#define IDC_COMBO_BENCH_QUEUE_5 1345 +#define IDC_COMBO_BENCH_QUEUE_8 1348 + +#define IDC_COMBO_BENCH_THREAD_0 1350 +#define IDC_COMBO_BENCH_THREAD_1 1351 +#define IDC_COMBO_BENCH_THREAD_2 1352 +#define IDC_COMBO_BENCH_THREAD_3 1353 +#define IDC_COMBO_BENCH_THREAD_4 1354 +#define IDC_COMBO_BENCH_THREAD_5 1355 +#define IDC_COMBO_BENCH_THREAD_8 1358 + +#define ID_EXIT 32771 +#define ID_ABOUT 32772 +#define ID_THEME 32775 +#define ID_THEME_DUMMY 32776 +#define ID_COPY 32777 +#define ID_LANGUAGE_DUMMY 32778 +#define ID_LANGUAGE_A 32779 +#define ID_LANGUAGE_O 32780 +#define ID_A_DUMMY 32781 +#define ID_O_DUMMY 32782 +#define ID_BACK_PAGE 32787 +#define ID_PRINT 32788 +#define ID_FUNCTION_ZOOM 32789 +#define ID_ZOOM_100 32803 +#define ID_ZOOM_125 32804 +#define ID_ZOOM_150 32805 +#define ID_ZOOM_200 32806 +#define ID_ZOOM_250 32807 +#define ID_ZOOM_300 32808 +#define ID_ZOOM_AUTO 33809 +#define ID_HELP_HELP 32810 +#define ID_CRYSTALDEWWORLD 32811 +#define ID_FILE_BENCHMARKMODE 32812 +#define ID_MODE_DEFAULT 32815 +#define ID_MODE_ALL0X00 32816 +#define ID_MODE_ALL0XFF 32817 +#define ID_SETTINGS_QUEUESTHREADS 32818 +#define ID_SAVE_TEXT 32819 +#define ID_SAVE_IMAGE 32820 +#define ID_FONT_SETTING 32821 +#define ID_SETTING_DEFAULT 32822 +#define ID_SETTING_NVME_8 32823 +#define ID_SETTING_FLASH_MEMORY 32824 + +#define ID_INTERVAL_TIME_0 33820 +#define ID_INTERVAL_TIME_1 33821 +#define ID_INTERVAL_TIME_3 33822 +#define ID_INTERVAL_TIME_5 33823 +#define ID_INTERVAL_TIME_10 33824 +#define ID_INTERVAL_TIME_30 33825 +#define ID_INTERVAL_TIME_60 33826 +#define ID_INTERVAL_TIME_180 33827 +#define ID_INTERVAL_TIME_300 33828 +#define ID_INTERVAL_TIME_600 33829 +#define ID_PROFILE_DEFAULT 33830 +#define ID_PROFILE_PEAK 33831 +#define ID_PROFILE_REAL 33832 +#define ID_PROFILE_DEMO 33833 +#define ID_PROFILE_DEFAULT_MIX 33834 +#define ID_PROFILE_PEAK_MIX 33835 +#define ID_PROFILE_REAL_MIX 33836 +#define ID_BENCHMARK_READ_WRITE 33837 +#define ID_BENCHMARK_READ_ONLY 33838 +#define ID_BENCHMARK_WRITE_ONLY 33839 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 141 +#define _APS_NEXT_COMMAND_VALUE 33840 +#define _APS_NEXT_CONTROL_VALUE 1360 +#define _APS_NEXT_SYMED_VALUE 107 +#endif +#endif diff --git a/CristalDiskMark/source/CrystalDiskMark/Library/stdafx.cpp b/CristalDiskMark/source/CrystalDiskMark/Library/stdafx.cpp new file mode 100644 index 0000000..ca35402 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Library/stdafx.cpp @@ -0,0 +1,5 @@ +// stdafx.cpp : source file that includes just the standard includes +// DiskInfo.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" diff --git a/CristalDiskMark/source/CrystalDiskMark/Library/stdafx.h b/CristalDiskMark/source/CrystalDiskMark/Library/stdafx.h new file mode 100644 index 0000000..a06429c --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Library/stdafx.h @@ -0,0 +1,228 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once + +#ifndef _SECURE_ATL +#define _SECURE_ATL 1 +#endif + +#ifndef VC_EXTRALEAN +#define VC_EXTRALEAN +#endif + +#ifndef WINVER +#define WINVER 0x0501 +#endif + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#endif + +#ifndef _WIN32_WINDOWS +#define _WIN32_WINDOWS 0x0410 +#endif + +#ifndef _WIN32_IE +#define _WIN32_IE 0x0600 +#endif + +#define _AFX_NO_MFC_CONTROLS_IN_DIALOGS +#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS +#define _AFX_ALL_WARNINGS + +#include // MFC core and standard component +#include // Extended MFC +#include // MFC IE4 Common Control support +#include // MFC Windows Common Control support + +#include "CommonFx.h" +#include "UtilityFx.h" + +#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") + +#ifdef UWP +#ifdef SUISHO_SHIZUKU_SUPPORT +#ifdef _M_ARM +#define PRODUCT_EDITION L"Shizuku Edition ARM32" +#elif _M_ARM64 +#define PRODUCT_EDITION L"Shizuku Edition ARM64" +#elif _M_X64 +#define PRODUCT_EDITION L"Shizuku Edition x64" +#else +#define PRODUCT_EDITION L"Shizuku Edition x86" +#endif +#else +#ifdef _M_ARM +#define PRODUCT_EDITION L"ARM32" +#elif _M_ARM64 +#define PRODUCT_EDITION L"ARM64" +#elif _M_X64 +#define PRODUCT_EDITION L"x64" +#else +#define PRODUCT_EDITION L"x86" +#endif +#endif + +#else + +#ifdef SUISHO_AOI_SUPPORT +#ifdef _M_ARM +#define PRODUCT_EDITION L"Aoi Edition ARM32" +#elif _M_ARM64 +#define PRODUCT_EDITION L"Aoi Edition ARM64" +#elif _M_X64 +#define PRODUCT_EDITION L"Aoi Edition x64" +#else +#define PRODUCT_EDITION L"Aoi Edition x86" +#endif + +#elif MSI_MEI_SUPPORT +#ifdef _M_ARM +#define PRODUCT_EDITION L"MSI Mei Mihoshi Edition ARM32" +#elif _M_ARM64 +#define PRODUCT_EDITION L"MSI Mei Mihoshi Edition ARM64" +#elif _M_X64 +#define PRODUCT_EDITION L"MSI Mei Mihoshi Edition x64" +#else +#define PRODUCT_EDITION L"MSI Mei Mihoshi Edition x86" +#endif + +#elif SUISHO_SHIZUKU_SUPPORT +#ifdef _M_ARM +#define PRODUCT_EDITION L"Shizuku Edition ARM32" +#elif _M_ARM64 +#define PRODUCT_EDITION L"Shizuku Edition ARM64" +#elif _M_X64 +#define PRODUCT_EDITION L"Shizuku Edition x64" +#else +#define PRODUCT_EDITION L"Shizuku Edition x86" +#endif + +#else +#ifdef _M_ARM +#define PRODUCT_EDITION L"ARM32" +#elif _M_ARM64 +#define PRODUCT_EDITION L"ARM64" +#elif _M_X64 +#define PRODUCT_EDITION L"x64" +#else +#define PRODUCT_EDITION L"x86" +#endif +#endif +#endif + +// Version Information +#define PRODUCT_NAME L"CrystalDiskMark" +#define PRODUCT_FILENAME L"CrystalDiskMark" +#define PRODUCT_VERSION L"9.0.3" +#define PRODUCT_SHORT_NAME L"CDM" + +#define PRODUCT_RELEASE L"2026/05/24" +#define PRODUCT_COPY_YEAR L"2007-2026" +#define PRODUCT_LICENSE L"MIT License" + +#ifdef SUISHO_AOI_SUPPORT +#define PRODUCT_COPYRIGHT_1 L"© 2007-2026 hiyohiyo" +#define PRODUCT_COPYRIGHT_2 L"© 2023-2026 nijihashi sola" +#define PRODUCT_COPYRIGHT_3 L"" + +#elif MSI_MEI_SUPPORT +#define PRODUCT_COPYRIGHT_1 L"© 2007-2026 hiyohiyo" +#define PRODUCT_COPYRIGHT_2 L"© 2024-2026 Micro-Star INT'L CO., LTD." +#define PRODUCT_COPYRIGHT_3 L"" + +#elif SUISHO_SHIZUKU_SUPPORT +#define PRODUCT_COPYRIGHT_1 L"© 2007-2026 hiyohiyo" +#define PRODUCT_COPYRIGHT_2 L"© 2012-2026 kirino kasumu" +#define PRODUCT_COPYRIGHT_3 L"" + +#else +#define PRODUCT_COPYRIGHT_1 L"© 2007-2026 hiyohiyo" +#define PRODUCT_COPYRIGHT_2 L"" +#define PRODUCT_COPYRIGHT_3 L"" +#endif + +#ifdef MSI_MEI_SUPPORT +#define URL_MAIN_JA L"https://jp.msi.com/" +#define URL_MAIN_EN L"https://www.msi.com/" +#else +#define URL_MAIN_JA L"https://crystalmark.info/ja/" +#define URL_MAIN_EN L"https://crystalmark.info/en/" +#endif + +#define URL_CRYSTAL_DEW_WORLD_JA L"https://crystalmark.info/ja/" +#define URL_CRYSTAL_DEW_WORLD_EN L"https://crystalmark.info/en/" + +#define URL_VERSION_JA L"https://crystalmark.info/ja/software/crystaldiskmark/crystaldiskmark-history/" +#define URL_VERSION_EN L"https://crystalmark.info/en/software/crystaldiskmark/crystaldiskmark-history/" +#define URL_LICENSE_JA L"https://crystalmark.info/ja/software/crystaldiskmark/crystaldiskmark-license/" +#define URL_LICENSE_EN L"https://crystalmark.info/en/software/crystaldiskmark/crystaldiskmark-license/" +#define URL_HELP_JA L"https://crystalmark.info/ja/software/crystaldiskmark/" +#define URL_HELP_EN L"https://crystalmark.info/en/software/crystaldiskmark/" + +#define URL_DISKSPD L"https://github.com/microsoft/diskspd" + +#ifdef SUISHO_AOI_SUPPORT +#define URL_PROJECT_SITE_1 L"https://twitter.com/sola_no_crayon" +#define URL_PROJECT_SITE_2 L"https://twitter.com/harakeiko0718" +#define URL_PROJECT_SITE_3 L"https://instagram.com/kotomi_wicke?igshid=OGQ5ZDc2ODk2ZA==" +#define URL_PROJECT_SITE_4 L"https://twitter.com/bellche" +#define URL_PROJECT_SITE_5 L"" + +#elif MSI_MEI_SUPPORT +#define URL_PROJECT_SITE_1 L"https://jp.msi.com/Landing/mihoshimei/nb" +#define URL_PROJECT_SITE_2 L"https://twitter.com/hoshi_u3" +#define URL_PROJECT_SITE_3 L"https://twitter.com/mokowata" +#define URL_PROJECT_SITE_4 L"https://jp.msi.com/" +#define URL_PROJECT_SITE_5 L"https://jp.msi.com/" + +#elif SUISHO_SHIZUKU_SUPPORT +#define URL_PROJECT_SITE_1 L"https://twitter.com/kirinokasumu" +#define URL_PROJECT_SITE_2 L"https://linux-ha.osdn.jp/wp/" +#define URL_PROJECT_SITE_3 L"https://ch.nicovideo.jp/oss" +#define URL_PROJECT_SITE_4 L"https://twitter.com/bellche" +#define URL_PROJECT_SITE_5 L"https://suishoshizuku.com/" +#endif + +#define MAX_THREADS 64 +#define MAX_QUEUES 512 + +static const int RE_EXEC = 5963; + +#pragma warning(disable : 4996) + +//------------------------------------------------ +// Option Flags +//------------------------------------------------ + +// For Task Tray Icon Feature +// #define OPTION_TASK_TRAY + +//------------------------------------------------ +// Global Sttings +//------------------------------------------------ + +#define DEFAULT_FONT_FACE_1 L"Segoe UI" +#define DEFAULT_FONT_FACE_2 L"Tahoma" + +#define THEME_DIR L"CdmResource\\themes\\" +#define LANGUAGE_DIR L"CdmResource\\language\\" +#define VOICE_DIR L"CdmResource\\voice\\" + + +#define MENU_THEME_INDEX 3 +#define MENU_LANG_INDEX 5 + +#define DEFAULT_THEME L"Default" +#define DEFAULT_LANGUAGE L"English" + +#define TIMER_UPDATE_DIALOG 500 + +#define WM_UPDATE_SCORE (WM_APP+0x1001) +#define WM_UPDATE_MESSAGE (WM_APP+0x1002) +#define WM_EXIT_BENCHMARK (WM_APP+0x1003) \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/ButtonFx.cpp b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ButtonFx.cpp new file mode 100644 index 0000000..60c4ef4 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ButtonFx.cpp @@ -0,0 +1,991 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "ButtonFx.h" + +#if _MSC_VER <= 1310 +#define ON_WM_MOUSEHOVER() \ + { 0x2A1 /*WM_MOUSEHOVER*/, 0, 0, 0, AfxSig_vwp, \ + (AFX_PMSG)(AFX_PMSGW) \ + (static_cast< void (AFX_MSG_CALL CWnd::*)(UINT, CPoint) > (OnMouseHover)) }, + +#define ON_WM_MOUSELEAVE() \ + { 0x2A3 /*WM_MOUSELEAVE*/, 0, 0, 0, AfxSig_vv, \ + (AFX_PMSG)(AFX_PMSGW) \ + (static_cast< void (AFX_MSG_CALL CWnd::*)(void) > (OnMouseLeave)) }, +#endif + +////------------------------------------------------ +// CButtonFx +////------------------------------------------------ + +CButtonFx::CButtonFx() +{ + // Control + m_X = 0; + m_Y = 0; + m_RenderMode = SystemDraw; + m_bHighContrast = FALSE; + m_bDarkMode = FALSE; + m_bDrawFrame = FALSE; + m_FrameColor = RGB(128, 128, 128); + m_hPal = NULL; + + // Glass + m_GlassColor = RGB(255, 255, 255); + m_GlassAlpha = 255; + + // Meter + m_bMeter = FALSE; + m_MeterRatio = 0.0; + + // Image + m_ImageCount = 0; + m_BkDC = NULL; + m_bBkBitmapInit = FALSE; + m_bBkLoad = FALSE; + + // Font + m_TextAlign = BS_LEFT; + m_TextColor = RGB(0, 0, 0); + + // Mouse + m_bHover = FALSE; + m_bFocas = FALSE; + m_bTrackingNow = FALSE; + m_bHandCursor = FALSE; + m_bSelected = FALSE; + + // Text Format + m_TextFormat = 0; + m_LabelFormat = DT_LEFT | DT_TOP | DT_SINGLELINE; + m_UnitFormat = DT_RIGHT | DT_BOTTOM | DT_SINGLELINE; + + // Margin + m_Margin.top = 0; + m_Margin.left = 0; + m_Margin.bottom = 0; + m_Margin.right = 0; +} + +CButtonFx::~CButtonFx() +{ +} + +IMPLEMENT_DYNAMIC(CButtonFx, CButton) + +BEGIN_MESSAGE_MAP(CButtonFx, CButton) + //{{AFX_MSG_MAP(CButtonFx) + ON_WM_ERASEBKGND() + ON_WM_MOUSEMOVE() + ON_WM_MOUSEHOVER() + ON_WM_MOUSELEAVE() + ON_WM_KILLFOCUS() + ON_WM_SETFOCUS() + ON_WM_SETCURSOR() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +//------------------------------------------------ +// Control +//------------------------------------------------ + +BOOL CButtonFx::InitControl(int x, int y, int width, int height, double zoomRatio, HPALETTE hPal, CDC* bkDC, + LPCTSTR imagePath, int imageCount, DWORD textAlign, int renderMode, BOOL bHighContrast, BOOL bDarkMode, BOOL bDrawFrame) +{ + m_X = (int)(x * zoomRatio); + m_Y = (int)(y * zoomRatio); + m_CtrlSize.cx = (int)(width * zoomRatio + 0.5); + m_CtrlSize.cy = (int)(height * zoomRatio + 0.5); + MoveWindow(m_X, m_Y, m_CtrlSize.cx, m_CtrlSize.cy); + + m_hPal = hPal; + m_BkDC = bkDC; + m_ImagePath = imagePath; + m_ImageCount = imageCount; + m_RenderMode = renderMode; + + if (BS_LEFT <= textAlign && textAlign <= BS_CENTER) + { + m_TextAlign = textAlign; + } + + if(m_ToolTip.m_hWnd != NULL) + { + if (m_ToolTip.GetToolCount() != 0) + { + m_ToolTip.DelTool(this, 1); + } + CRect rect; + GetClientRect(rect); + m_ToolTip.AddTool(this, m_ToolTipText, rect, 1); + } + + m_bHighContrast = bHighContrast; + m_bDarkMode = bDarkMode; + m_bDrawFrame = bDrawFrame; + + if (m_bHighContrast) + { + ModifyStyle(BS_OWNERDRAW, m_TextAlign); + + return TRUE; + } + else if(renderMode & SystemDraw) + { + ModifyStyle(BS_OWNERDRAW, m_TextAlign); + + return TRUE; + } + else + { + SetBkReload(); + ModifyStyle(0, BS_OWNERDRAW); + } + + if (renderMode & OwnerDrawImage) + { + if (!LoadBitmap(imagePath)) + { + ModifyStyle(BS_OWNERDRAW, m_TextAlign); + } + } + else + { + m_ImageCount = 1; + m_CtrlImage.Destroy(); + m_CtrlImage.Create(m_CtrlSize.cx, m_CtrlSize.cy * m_ImageCount, 32); + m_CtrlBitmap.Detach(); + m_CtrlBitmap.Attach((HBITMAP)m_CtrlImage); + DWORD length = m_CtrlSize.cx * m_CtrlSize.cy * m_ImageCount * 4; + BYTE* bitmapBits = new BYTE[length]; + m_CtrlBitmap.GetBitmapBits(length, bitmapBits); + + BYTE r, g, b, a; + if (renderMode & OwnerDrawGlass) + { + r = (BYTE)GetRValue(m_GlassColor); + g = (BYTE)GetGValue(m_GlassColor); + b = (BYTE)GetBValue(m_GlassColor); + a = m_GlassAlpha; + } + else // OwnerDrawTransparent + { + r = 0; + g = 0; + b = 0; + a = 0; + } + + for (int y = 0; y < (int)(m_CtrlSize.cy * m_ImageCount); y++) + { + for (int x = 0; x < m_CtrlSize.cx; x++) + { + DWORD p = (y * m_CtrlSize.cx + x) * 4; +#if _MSC_VER > 1310 +#pragma warning( disable : 6386 ) +#endif + bitmapBits[p + 0] = b; + bitmapBits[p + 1] = g; + bitmapBits[p + 2] = r; + bitmapBits[p + 3] = a; +#if _MSC_VER > 1310 +#pragma warning( default : 6386 ) +#endif + } + } + + m_CtrlBitmap.SetBitmapBits(length, bitmapBits); + delete[] bitmapBits; + } + + Invalidate(); + + return TRUE; +} + +BOOL CButtonFx::ReloadImage(LPCTSTR imagePath, UINT imageCount) +{ + if (imagePath != NULL && m_ImagePath.Compare(imagePath) == 0) + { + return FALSE; + } + + m_ImagePath = imagePath; + m_ImageCount = imageCount; + + LoadBitmap(imagePath); + + Invalidate(); + return TRUE; +} + +void CButtonFx::SetMargin(int top, int left, int bottom, int right, double zoomRatio) +{ + m_Margin.top = (int)(top * zoomRatio); + m_Margin.left = (int)(left * zoomRatio); + m_Margin.bottom = (int)(bottom * zoomRatio); + m_Margin.right = (int)(right * zoomRatio); +} + +CSize CButtonFx::GetSize(void) +{ + return m_CtrlSize; +} + +void CButtonFx::SetDrawFrame(BOOL bDrawFrame) +{ + if (bDrawFrame && m_bHighContrast) + { + ModifyStyleEx(0, WS_EX_STATICEDGE, SWP_DRAWFRAME); + } + else + { + ModifyStyleEx(WS_EX_STATICEDGE, 0, SWP_DRAWFRAME); + } + m_bDrawFrame = bDrawFrame; +} + +void CButtonFx::SetGlassColor(COLORREF glassColor, BYTE glassAlpha) +{ + m_GlassColor = glassColor; + m_GlassAlpha = glassAlpha; +} + +void CButtonFx::SetMeter(BOOL bMeter, double meterRatio) +{ + m_bMeter = bMeter; + if (meterRatio > 1.0) + { + m_MeterRatio = 1.0; + } + else if (meterRatio > 0) + { + m_MeterRatio = meterRatio; + } + else + { + m_MeterRatio = 0.0; + } + + Invalidate(); +} + +void CButtonFx::SetLabelUnit(CString label, CString unit) +{ + m_Label = label; + m_Unit = unit; +} + +void CButtonFx::SetLabelUnitFormat(UINT labelFormat, UINT unitFormat) +{ + m_LabelFormat = labelFormat; + m_UnitFormat = unitFormat; +} + +void CButtonFx::SetTextFormat(UINT format) +{ + m_TextFormat = format; +} + +//------------------------------------------------ +// Draw Control +//------------------------------------------------ + +void CButtonFx::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) +{ + if (m_bHighContrast) { return CButton::DrawItem(lpDrawItemStruct); } + + CDC* drawDC = CDC::FromHandle(lpDrawItemStruct->hDC); + LoadCtrlBk(drawDC); + + if (! (GetStyle() & BS_NOTIFY)) + { + DrawControl(drawDC, lpDrawItemStruct, m_CtrlBitmap, m_BkBitmap, ControlImageNormal); + } + else if (IsWindowEnabled()) + { + if (m_bSelected && m_ImageCount > ControlImageSelected) + { + DrawControl(drawDC, lpDrawItemStruct, m_CtrlBitmap, m_BkBitmap, ControlImageSelected); + } + else if ((lpDrawItemStruct->itemState & ODS_SELECTED || m_bHover) && m_ImageCount > ControlImageHover) + { + DrawControl(drawDC, lpDrawItemStruct, m_CtrlBitmap, m_BkBitmap, ControlImageHover); + } + else if ((lpDrawItemStruct->itemState & ODS_FOCUS || m_bFocas) && m_ImageCount > ControlImageFocus) + { + DrawControl(drawDC, lpDrawItemStruct, m_CtrlBitmap, m_BkBitmap, ControlImageFocus); + } + else + { + DrawControl(drawDC, lpDrawItemStruct, m_CtrlBitmap, m_BkBitmap, ControlImageNormal); + } + } + else + { + if (m_ImageCount > ControlImageDisabled) + { + DrawControl(drawDC, lpDrawItemStruct, m_CtrlBitmap, m_BkBitmap, ControlImageDisabled); + } + else + { + DrawControl(drawDC, lpDrawItemStruct, m_CtrlBitmap, m_BkBitmap, ControlImageNormal); + } + } +} + +void CButtonFx::DrawControl(CDC* drawDC, LPDRAWITEMSTRUCT lpDrawItemStruct, CBitmap& ctrlBitmap, CBitmap& bkBitmap, int no) +{ + CDC* pMemDC = new CDC; + CBitmap* pOldMemBitmap; + if(m_hPal && drawDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE) + { + SelectPalette( drawDC->GetSafeHdc(), m_hPal, FALSE ); + drawDC->RealizePalette(); + drawDC->SetStretchBltMode(HALFTONE); + } + pMemDC->CreateCompatibleDC(drawDC); + if(m_hPal && pMemDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE) + { + SelectPalette( pMemDC->GetSafeHdc(), m_hPal, FALSE ); + pMemDC->RealizePalette(); + pMemDC->SetStretchBltMode(HALFTONE); + } + pOldMemBitmap = pMemDC->SelectObject(&ctrlBitmap); + CDC* pBkDC = new CDC; + CBitmap* pOldBkBitmap; + pBkDC->CreateCompatibleDC(drawDC); + if(m_hPal && pBkDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE) + { + SelectPalette( pBkDC->GetSafeHdc(), m_hPal, FALSE ); + pBkDC->RealizePalette(); + pBkDC->SetStretchBltMode(HALFTONE); + } + pOldBkBitmap = pBkDC->SelectObject(&bkBitmap); + + CBitmap DrawBmp; + DrawBmp.CreateCompatibleBitmap(drawDC, m_CtrlSize.cx, m_CtrlSize.cy); + CDC* pDrawBmpDC = new CDC; + CBitmap* pOldDrawBitmap; + pDrawBmpDC->CreateCompatibleDC(drawDC); + if(m_hPal && pDrawBmpDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE) + { + SelectPalette( pDrawBmpDC->GetSafeHdc(), m_hPal, FALSE ); + pDrawBmpDC->RealizePalette(); + pDrawBmpDC->SetStretchBltMode(HALFTONE); + } + pOldDrawBitmap = pDrawBmpDC->SelectObject(&DrawBmp); + + int color = drawDC->GetDeviceCaps(BITSPIXEL) * drawDC->GetDeviceCaps(PLANES); + + if (!m_CtrlImage.IsNull()) + { + if (m_CtrlImage.GetBPP() == 32) + { + CBitmap* bk32Bitmap; + CImage bk32Image; + if (color == 32) + { + bk32Bitmap = &bkBitmap; + } + else + { + bk32Image.Create(m_CtrlSize.cx, m_CtrlSize.cy, 32); + ::StretchBlt(bk32Image.GetDC(), 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, *pBkDC, 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, SRCCOPY); + bk32Bitmap = CBitmap::FromHandle((HBITMAP)bk32Image); + } + + BITMAP CtlBmpInfo, DstBmpInfo; + bk32Bitmap->GetBitmap(&DstBmpInfo); + DWORD DstLineBytes = DstBmpInfo.bmWidthBytes; + DWORD DstMemSize = DstLineBytes * DstBmpInfo.bmHeight; + ctrlBitmap.GetBitmap(&CtlBmpInfo); + DWORD CtlLineBytes = CtlBmpInfo.bmWidthBytes; + DWORD CtlMemSize = CtlLineBytes * CtlBmpInfo.bmHeight; + + if ((DstBmpInfo.bmWidthBytes != CtlBmpInfo.bmWidthBytes) + || (DstBmpInfo.bmHeight != CtlBmpInfo.bmHeight / m_ImageCount)) + { + // Error Check // + } + else + { + BYTE* DstBuffer = new BYTE[DstMemSize]; + bk32Bitmap->GetBitmapBits(DstMemSize, DstBuffer); + BYTE* CtlBuffer = new BYTE[CtlMemSize]; + ctrlBitmap.GetBitmapBits(CtlMemSize, CtlBuffer); + + if (m_bMeter) + { + int meter = (int)(m_CtrlSize.cx * m_MeterRatio); + int baseY; + baseY = m_CtrlSize.cy; + for (LONG py = 0; py < DstBmpInfo.bmHeight; py++) + { + int dn = py * DstLineBytes; + int cn = (baseY + py) * CtlLineBytes; + for (LONG px = 0; px < meter; px++) + { + BYTE a = CtlBuffer[cn + 3]; + BYTE na = 255 - a; + DstBuffer[dn + 0] = (BYTE)((CtlBuffer[cn + 0] * a + DstBuffer[dn + 0] * na) / 255); + DstBuffer[dn + 1] = (BYTE)((CtlBuffer[cn + 1] * a + DstBuffer[dn + 1] * na) / 255); + DstBuffer[dn + 2] = (BYTE)((CtlBuffer[cn + 2] * a + DstBuffer[dn + 2] * na) / 255); + dn += (DstBmpInfo.bmBitsPixel / 8); + cn += (CtlBmpInfo.bmBitsPixel / 8); + } + cn -= baseY * CtlLineBytes; + for (LONG px = meter; px < DstBmpInfo.bmWidth; px++) + { + BYTE a = CtlBuffer[cn + 3]; + BYTE na = 255 - a; + DstBuffer[dn + 0] = (BYTE)((CtlBuffer[cn + 0] * a + DstBuffer[dn + 0] * na) / 255); + DstBuffer[dn + 1] = (BYTE)((CtlBuffer[cn + 1] * a + DstBuffer[dn + 1] * na) / 255); + DstBuffer[dn + 2] = (BYTE)((CtlBuffer[cn + 2] * a + DstBuffer[dn + 2] * na) / 255); + dn += (DstBmpInfo.bmBitsPixel / 8); + cn += (CtlBmpInfo.bmBitsPixel / 8); + } + } + } + else + { + int baseY = m_CtrlSize.cy * no; + for (LONG py = 0; py < DstBmpInfo.bmHeight; py++) + { + int dn = py * DstLineBytes; + int cn = (baseY + py) * CtlLineBytes; + for (LONG px = 0; px < DstBmpInfo.bmWidth; px++) + { +#if _MSC_VER > 1310 +#pragma warning( disable : 6385 ) +#pragma warning( disable : 6386 ) +#endif + BYTE a = CtlBuffer[cn + 3]; + BYTE na = 255 - a; + DstBuffer[dn + 0] = (BYTE)((CtlBuffer[cn + 0] * a + DstBuffer[dn + 0] * na) / 255); + DstBuffer[dn + 1] = (BYTE)((CtlBuffer[cn + 1] * a + DstBuffer[dn + 1] * na) / 255); + DstBuffer[dn + 2] = (BYTE)((CtlBuffer[cn + 2] * a + DstBuffer[dn + 2] * na) / 255); + dn += (DstBmpInfo.bmBitsPixel / 8); + cn += (CtlBmpInfo.bmBitsPixel / 8); +#if _MSC_VER > 1310 +#pragma warning( default : 6386 ) +#pragma warning( default : 6385 ) +#endif + } + } + } + + if (color == 32) + { + DrawBmp.SetBitmapBits(DstMemSize, DstBuffer); + } + else + { + bk32Bitmap->SetBitmapBits(DstMemSize, DstBuffer); + ::StretchBlt(pDrawBmpDC->GetSafeHdc(), 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, bk32Image.GetDC(), 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, SRCCOPY); + bk32Image.ReleaseDC(); + } + DrawString(pDrawBmpDC, lpDrawItemStruct); + drawDC->StretchBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, pDrawBmpDC, 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, SRCCOPY); + + delete[] DstBuffer; + delete[] CtlBuffer; + } + } + else + { + if (m_bMeter) + { + int meter = (int)(m_CtrlSize.cx * (m_MeterRatio)); + pDrawBmpDC->StretchBlt(meter, 0, m_CtrlSize.cx - meter, m_CtrlSize.cy, pMemDC, meter, m_CtrlSize.cy * 0, m_CtrlSize.cx - meter, m_CtrlSize.cy, SRCCOPY); + pDrawBmpDC->StretchBlt(0, 0, meter, m_CtrlSize.cy, pMemDC, 0, m_CtrlSize.cy * 1, meter, m_CtrlSize.cy, SRCCOPY); + DrawString(pDrawBmpDC, lpDrawItemStruct); + drawDC->StretchBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, pDrawBmpDC, 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, SRCCOPY); + } + else + { + pDrawBmpDC->StretchBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, pMemDC, 0, m_CtrlSize.cy * no, m_CtrlSize.cx, m_CtrlSize.cy, SRCCOPY); + DrawString(pDrawBmpDC, lpDrawItemStruct); + drawDC->StretchBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, pDrawBmpDC, 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, SRCCOPY); + } + } + } + else + { + pDrawBmpDC->StretchBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, pBkDC, 0, m_CtrlSize.cy * no, m_CtrlSize.cx, m_CtrlSize.cy, SRCCOPY); + DrawString(pDrawBmpDC, lpDrawItemStruct); + drawDC->StretchBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, pDrawBmpDC, 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, SRCCOPY); + } + + if (m_bDrawFrame) + { + HGDIOBJ oldPen; + POINT point; + + COLORREF color1; + COLORREF color2; + + if (m_bHover) + { + color1 = RGB(0x20, 0x98, 0xF4); + color2 = RGB(0x20, 0x8B, 0xDE); + } + else + { + // Windows 11 color + color1 = RGB(0x00, 0x78, 0xD4); + color2 = RGB(0x00, 0x6B, 0xBE); + } + + CPen pen1; pen1.CreatePen(PS_SOLID, 1, color1); + CPen pen2; pen2.CreatePen(PS_SOLID, 1, color2); + + oldPen = SelectObject(drawDC->m_hDC, pen1); + MoveToEx(drawDC->m_hDC, 0, m_CtrlSize.cy - 1, &point); + LineTo(drawDC->m_hDC, m_CtrlSize.cx - 1, m_CtrlSize.cy - 1); + LineTo(drawDC->m_hDC, m_CtrlSize.cx - 1, 0); + LineTo(drawDC->m_hDC, m_CtrlSize.cx - 1, m_CtrlSize.cy - 1); + SelectObject(drawDC->m_hDC, pen2); + MoveToEx(drawDC->m_hDC, 0, m_CtrlSize.cy - 2, &point); + LineTo(drawDC->m_hDC, 0, 0); + LineTo(drawDC->m_hDC, m_CtrlSize.cx - 1, 0); + SelectObject(drawDC->m_hDC, oldPen); + + pen1.DeleteObject(); + pen2.DeleteObject(); + } + + pDrawBmpDC->SelectObject(&pOldDrawBitmap); + pDrawBmpDC->DeleteDC(); + delete pDrawBmpDC; + pMemDC->SelectObject(&pOldMemBitmap); + pMemDC->DeleteDC(); + delete pMemDC; + pBkDC->SelectObject(&pOldBkBitmap); + pBkDC->DeleteDC(); + delete pBkDC; +} + +void CButtonFx::DrawString(CDC* drawDC, LPDRAWITEMSTRUCT lpDrawItemStruct) +{ + CString title; + GetWindowText(title); + + if (title.IsEmpty()) + { + return; + } + + drawDC->SetBkMode(TRANSPARENT); + CRect rect = (CRect)(lpDrawItemStruct->rcItem); + rect.top += m_Margin.top; + rect.left += m_Margin.left; + rect.bottom -= m_Margin.bottom; + rect.right -= m_Margin.right; + + HGDIOBJ oldFont = drawDC->SelectObject(m_Font); + CArray arr; + arr.RemoveAll(); + + CString resToken; + int curPos = 0; + resToken = title.Tokenize(_T("\r\n"), curPos); + while (resToken != _T("")) + { + arr.Add(resToken); + resToken = title.Tokenize(_T("\r\n"), curPos); + } + + CSize extent; + if ((m_RenderMode & OwnerDrawTransparent) && m_bDarkMode) + { + SetTextColor(drawDC->m_hDC, RGB(255, 255, 255)); + } + else + { + SetTextColor(drawDC->m_hDC, m_TextColor); + } + + if (m_bMeter && rect.Width() < extent.cx) + { + title.Replace(_T(","), _T(".")); + int score = _tstoi((LPCTSTR)title); + title.Format(_T("%d"), score); + extent = drawDC->GetTextExtent(title); + } + + if (!m_Label.IsEmpty() && m_TextFormat != BS_CENTER) + { + drawDC->DrawText(title, title.GetLength(), rect, m_TextFormat); + drawDC->SelectObject(oldFont); + + oldFont = drawDC->SelectObject(m_FontToolTip); + drawDC->DrawText(m_Label, m_Label.GetLength(), rect, m_LabelFormat); + drawDC->DrawText(m_Unit, m_Unit.GetLength(), rect, m_UnitFormat); + } + else if (!m_Label.IsEmpty()) + { + drawDC->DrawText(title, title.GetLength(), rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + drawDC->SelectObject(oldFont); + + oldFont = drawDC->SelectObject(m_FontToolTip); + drawDC->DrawText(m_Label, m_Label.GetLength(), rect, m_LabelFormat); + drawDC->DrawText(m_Unit, m_Unit.GetLength(), rect, m_UnitFormat); + } + else + { + for (int i = 0; i < arr.GetCount(); i++) + { + CRect r; + r.top = rect.top + (LONG)(((double)rect.Height()) / arr.GetCount() * i); + r.bottom = rect.top + (LONG)(((double)rect.Height()) / arr.GetCount() * (i + 1.0)); + r.left = rect.left; + r.right = rect.right; + + CRect rectI; + HGDIOBJ oldFont = drawDC->SelectObject(m_Font); + if ((m_RenderMode & OwnerDrawTransparent) && m_bDarkMode) + { + SetTextColor(drawDC->m_hDC, RGB(255, 255, 255)); + } + else + { + SetTextColor(drawDC->m_hDC, m_TextColor); + } + GetTextExtentPoint32(drawDC->m_hDC, arr.GetAt(i), arr.GetAt(i).GetLength() + 1, &extent); + + if (m_TextAlign == BS_LEFT) + { + drawDC->DrawText(arr.GetAt(i), arr.GetAt(i).GetLength(), r, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + } + else if (m_TextAlign == BS_RIGHT) + { + drawDC->DrawText(arr.GetAt(i), arr.GetAt(i).GetLength(), r, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); + } + else + { + drawDC->DrawText(arr.GetAt(i), arr.GetAt(i).GetLength(), r, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + } + + drawDC->SelectObject(oldFont); + } + } +} + +//------------------------------------------------ +// Image +//------------------------------------------------ + +BOOL CButtonFx::LoadBitmap(LPCTSTR fileName) +{ + if (m_bHighContrast) { return FALSE; } + if (fileName == NULL) { return FALSE; } + + m_CtrlImage.Destroy(); + m_CtrlImage.Load(fileName); + if (m_CtrlImage.IsNull()) { return FALSE; } + + return LoadBitmap((HBITMAP)m_CtrlImage); +} + +BOOL CButtonFx::LoadBitmap(HBITMAP hBitmap) +{ + if (m_bHighContrast) { return FALSE; } + + m_CtrlBitmap.Detach(); + m_CtrlBitmap.Attach(hBitmap); + + return SetBitmap(m_CtrlBitmap); +} + +void CButtonFx::SetBkReload(void) +{ + m_bBkBitmapInit = FALSE; + m_bBkLoad = FALSE; +} + +BOOL CButtonFx::SetBitmap(CBitmap& bitmap) +{ + if (m_bHighContrast) { return FALSE; } + + BITMAP bitmapInfo; + bitmap.GetBitmap(&bitmapInfo); + + if (m_CtrlSize.cx != bitmapInfo.bmWidth + || m_CtrlSize.cy != bitmapInfo.bmHeight / m_ImageCount) + { + ModifyStyle(BS_OWNERDRAW, 0); + return FALSE; + } + else + { + ModifyStyle(0, BS_OWNERDRAW); + return TRUE; + } +} + +void CButtonFx::LoadCtrlBk(CDC* drawDC) +{ + if (m_bHighContrast) { SetBkReload(); return; } + + if (m_BkBitmap.m_hObject != NULL) + { + BITMAP bitmapInfo; + m_BkBitmap.GetBitmap(&bitmapInfo); + if (bitmapInfo.bmBitsPixel != drawDC->GetDeviceCaps(BITSPIXEL)) + { + SetBkReload(); + } + } + + if (&m_CtrlBitmap != NULL) + { + if (!m_bBkBitmapInit) + { + m_BkBitmap.DeleteObject(); + m_BkBitmap.CreateCompatibleBitmap(drawDC, m_CtrlSize.cx, m_CtrlSize.cy); + m_bBkBitmapInit = TRUE; + } + + if (!m_bBkLoad) + { + CBitmap* pOldBitmap; + CDC* pMemDC = new CDC; + pMemDC->CreateCompatibleDC(drawDC); + pOldBitmap = pMemDC->SelectObject(&m_BkBitmap); + pMemDC->StretchBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, m_BkDC, m_X, m_Y, m_CtrlSize.cx, m_CtrlSize.cy, SRCCOPY); + pMemDC->SelectObject(pOldBitmap); + pMemDC->DeleteDC(); + delete pMemDC; + m_bBkLoad = TRUE; + } + } +} + +//------------------------------------------------ +// Font +//------------------------------------------------ + +void CButtonFx::SetFontEx(CString face, int size, int sizeToolTip, double zoomRatio, double fontRatio, COLORREF textColor, LONG fontWeight, BYTE fontRender) +{ + LOGFONT logFont = { 0 }; + logFont.lfCharSet = DEFAULT_CHARSET; + logFont.lfHeight = (LONG)(-1 * size * zoomRatio * fontRatio); + logFont.lfQuality = fontRender; + logFont.lfWeight = fontWeight; + + if (face.GetLength() < 32) + { + wsprintf(logFont.lfFaceName, _T("%s"), (LPCTSTR)face); + } + else + { + wsprintf(logFont.lfFaceName, _T("")); + } + + m_Font.DeleteObject(); + m_Font.CreateFontIndirect(&logFont); + SetFont(&m_Font); + + logFont.lfHeight = (LONG)(-1 * sizeToolTip * zoomRatio * fontRatio); + m_FontToolTip.DeleteObject(); + m_FontToolTip.CreateFontIndirect(&logFont); + + m_TextColor = textColor; + + if (m_ToolTip.m_hWnd != NULL) + { + m_ToolTip.SetFont(&m_FontToolTip); + } +} + +//------------------------------------------------ +// Mouse +//------------------------------------------------ + +void CButtonFx::SetHandCursor(BOOL bHandCuror) +{ + m_bHandCursor = bHandCuror; +} + +void CButtonFx::OnMouseMove(UINT nFlags, CPoint point) +{ + if (!m_bTrackingNow) + { +#if _MSC_VER <= 1310 + typedef BOOL(WINAPI* Func_TrackMouseEvent)(LPTRACKMOUSEEVENT); + static Func_TrackMouseEvent p_TrackMouseEvent = NULL; + static BOOL bInit_TrackMouseEvent = FALSE; + + if (bInit_TrackMouseEvent && p_TrackMouseEvent == NULL) + { + return; // TrackMouseEvent is not available + } + else + { + HMODULE hModule = GetModuleHandle(_T("user32.dll")); + if (hModule) + { + p_TrackMouseEvent = (Func_TrackMouseEvent)GetProcAddress(hModule, "TrackMouseEvent"); + } + } + + if (p_TrackMouseEvent != NULL) + { + TRACKMOUSEEVENT tme = { sizeof(TRACKMOUSEEVENT) }; + tme.hwndTrack = m_hWnd; + tme.dwFlags = TME_LEAVE | TME_HOVER; + tme.dwHoverTime = 1; + m_bTrackingNow = p_TrackMouseEvent(&tme); + } + bInit_TrackMouseEvent = TRUE; +#else + if (!m_bTrackingNow) + { + TRACKMOUSEEVENT tme = { sizeof(TRACKMOUSEEVENT) }; + tme.hwndTrack = m_hWnd; + tme.dwFlags = TME_LEAVE | TME_HOVER; + tme.dwHoverTime = 1; + m_bTrackingNow = _TrackMouseEvent(&tme); + } +#endif + } + + CButton::OnMouseMove(nFlags, point); +} + +void CButtonFx::OnMouseHover(UINT nFlags, CPoint point) +{ +#if _MSC_VER > 1310 + CButton::OnMouseHover(nFlags, point); +#endif + + m_bHover = TRUE; + Invalidate(); +} + +void CButtonFx::OnMouseLeave() +{ +#if _MSC_VER > 1310 + CButton::OnMouseLeave(); +#endif + + m_bTrackingNow = FALSE; + m_bHover = FALSE; + Invalidate(); +} + +void CButtonFx::OnSetfocus() +{ + m_bFocas = TRUE; + Invalidate(); +} + +void CButtonFx::OnKillfocus() +{ + m_bFocas = FALSE; + Invalidate(); +} + +BOOL CButtonFx::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) +{ + HCURSOR hCursor = NULL; + if (m_bHandCursor) + { + hCursor = AfxGetApp()->LoadStandardCursor(IDC_HAND); + if (hCursor) + { + ::SetCursor(hCursor); + } + } + else + { + hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW); + if (hCursor) + { + ::SetCursor(hCursor); + } + } + + return TRUE; +} + +void CButtonFx::SetSelected(BOOL bSelected) +{ + m_bSelected = bSelected; + Invalidate(); +} + +//------------------------------------------------ +// ToolTip +//------------------------------------------------ + +void CButtonFx::SetToolTipText(LPCTSTR text) +{ + if (text == NULL) { return; } + + InitToolTip(); + m_ToolTipText = text; + if (m_ToolTip.GetToolCount() == 0) + { + CRect rect; + GetClientRect(rect); + m_ToolTip.AddTool(this, m_ToolTipText, rect, 1); + } + else + { + m_ToolTip.UpdateTipText(m_ToolTipText, this, 1); + } + + SetToolTipActivate(TRUE); +} + +void CButtonFx::SetToolTipActivate(BOOL bActivate) +{ + if (m_ToolTip.GetToolCount() == 0) { return; } + m_ToolTip.Activate(bActivate); +} + +void CButtonFx::SetToolTipWindowText(LPCTSTR text) +{ + SetToolTipText(text); + SetWindowText(text); +} + +CString CButtonFx::GetToolTipText() +{ + return m_ToolTipText; +} + +void CButtonFx::InitToolTip() +{ + if (m_ToolTip.m_hWnd == NULL) + { + m_ToolTip.Create(this, TTS_ALWAYSTIP | TTS_BALLOON | TTS_NOANIMATE | TTS_NOFADE); + m_ToolTip.Activate(FALSE); + m_ToolTip.SetFont(&m_FontToolTip); + m_ToolTip.SendMessage(TTM_SETMAXTIPWIDTH, 0, 1024); + m_ToolTip.SetDelayTime(TTDT_AUTOPOP, 8000); + m_ToolTip.SetDelayTime(TTDT_INITIAL, 500); + m_ToolTip.SetDelayTime(TTDT_RESHOW, 100); + } +} + +BOOL CButtonFx::PreTranslateMessage(MSG* pMsg) +{ + InitToolTip(); + m_ToolTip.RelayEvent(pMsg); + + return CButton::PreTranslateMessage(pMsg); +} + +BOOL CButtonFx::OnEraseBkgnd(CDC* pDC) +{ + return TRUE; +} \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/ButtonFx.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ButtonFx.h new file mode 100644 index 0000000..0431d18 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ButtonFx.h @@ -0,0 +1,131 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once + +#include + +#include "ImageFx.h" + +class CButtonFx : public CButton +{ + DECLARE_DYNAMIC(CButtonFx); + +public: + // Constructors + CButtonFx(); + virtual ~CButtonFx(); + + // Control + BOOL InitControl(int x, int y, int width, int height, double zoomRatio, HPALETTE hPal, CDC* bkDC, + LPCTSTR imagePath, int imageCount, DWORD textAlign, int renderMode, BOOL bHighContrast, BOOL bDarkMode, BOOL bDrawFrame); + BOOL ReloadImage(LPCTSTR imagePath, UINT imageCount); + void SetMargin(int top, int left, int bottom, int right, double zoomRatio); + CSize GetSize(void); + void SetDrawFrame(BOOL bDrawFrame); + void SetGlassColor(COLORREF glassColor, BYTE glassAlpha); + void SetMeter(BOOL bMeter, double meterRatio); + void SetLabelUnit(CString label, CString unit); + void SetLabelUnitFormat(UINT labelFormat, UINT unitFormat); + void SetTextFormat(UINT format); + + // Font + void SetFontEx(CString face, int size, int sizeToolTip, double zoomRatio, double fontRatio = 1.0, + COLORREF textColor = RGB(0, 0, 0), LONG fontWeight = FW_NORMAL, BYTE fontRender = CLEARTYPE_NATURAL_QUALITY); + + // Mouse + void SetHandCursor(BOOL bHandCuror = TRUE); + void SetSelected(BOOL bSelected = TRUE); + + // ToolTip + void SetToolTipText(LPCTSTR text); + void SetToolTipActivate(BOOL bActivate = TRUE); + void SetToolTipWindowText(LPCTSTR text); + CString GetToolTipText(); + +protected: + // Draw Control + virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); + virtual void DrawControl(CDC* drawDC, LPDRAWITEMSTRUCT lpDrawItemStruct, CBitmap& ctrlBitmap, CBitmap& bkBitmap, int no); + virtual void DrawString(CDC* drawDC, LPDRAWITEMSTRUCT lpDrawItemStruct); + + // Image + BOOL LoadBitmap(LPCTSTR pFileName); + BOOL LoadBitmap(HBITMAP hBitmap); + void SetBkReload(void); + BOOL SetBitmap(CBitmap& bitmap); + void LoadCtrlBk(CDC* drawDC); + + // ToolTip + void InitToolTip(); + virtual BOOL PreTranslateMessage(MSG* pMsg); + + // Message Map + DECLARE_MESSAGE_MAP() + afx_msg BOOL OnEraseBkgnd(CDC* pDC); + afx_msg void OnMouseMove(UINT nFlags, CPoint point); + afx_msg void OnMouseHover(UINT nFlags, CPoint point); + afx_msg void OnMouseLeave(); + afx_msg void OnKillfocus(); + afx_msg void OnSetfocus(); + afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message); + +protected: + // Control + int m_X; + int m_Y; + CSize m_CtrlSize; + CRect m_Margin; + int m_RenderMode; + BOOL m_bHighContrast; + BOOL m_bDarkMode; + BOOL m_bDrawFrame; + COLORREF m_FrameColor; + HPALETTE m_hPal; + + CString m_Label; + CString m_Unit; + + UINT m_TextFormat; + UINT m_LabelFormat; + UINT m_UnitFormat; + + // Glass + COLORREF m_GlassColor; + BYTE m_GlassAlpha; + + // Meter + BOOL m_bMeter; + double m_MeterRatio; + + // Image + CString m_ImagePath; + int m_ImageCount; + CDC* m_BkDC; + CBitmap m_BkBitmap; + BOOL m_bBkBitmapInit; + BOOL m_bBkLoad; + CBitmap m_CtrlBitmap; + CImage m_CtrlImage; + + // Font + DWORD m_TextAlign; + CFont m_Font; + CFont m_FontToolTip; + COLORREF m_TextColor; + + // ToolTip + CToolTipCtrl m_ToolTip; + CString m_ToolTipText; + + // Mouse + BOOL m_bHover; + BOOL m_bFocas; + BOOL m_bTrackingNow; + BOOL m_bHandCursor; + BOOL m_bSelected; +}; diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/ComboBoxFx.cpp b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ComboBoxFx.cpp new file mode 100644 index 0000000..83237c9 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ComboBoxFx.cpp @@ -0,0 +1,836 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "ComboBoxFx.h" + +#if _MSC_VER <= 1310 +#define ON_WM_MOUSEHOVER() \ + { 0x2A1 /*WM_MOUSEHOVER*/, 0, 0, 0, AfxSig_vwp, \ + (AFX_PMSG)(AFX_PMSGW) \ + (static_cast< void (AFX_MSG_CALL CWnd::*)(UINT, CPoint) > (OnMouseHover)) }, + +#define ON_WM_MOUSELEAVE() \ + { 0x2A3 /*WM_MOUSELEAVE*/, 0, 0, 0, AfxSig_vv, \ + (AFX_PMSG)(AFX_PMSGW) \ + (static_cast< void (AFX_MSG_CALL CWnd::*)(void) > (OnMouseLeave)) }, +#endif + +////------------------------------------------------ +// CComboBoxFx +////------------------------------------------------ + +CComboBoxFx::CComboBoxFx() +{ + // Control + m_X = 0; + m_Y = 0; + m_ZoomRatio = 1.0; + m_bHighContrast = FALSE; + m_bDarkMode = FALSE; + m_RenderMode = SystemDraw; + m_Margin.top = 0; + m_Margin.left = 0; + m_Margin.bottom = 0; + m_Margin.right = 0; + + // Alpha/Glass + m_Alpha = 255; + m_GlassColor = RGB(255, 255, 255); + m_GlassAlpha = 255; + + // Image + m_ImageCount = 0; + m_ImagePath = _T(""); + m_BkDC = NULL; + m_bBkBitmapInit = FALSE; + m_bBkLoad = FALSE; + + // Font + m_TextAlign = SS_LEFT; + m_TextColor = RGB(0, 0, 0); + m_TextColorSelected = RGB(255, 255, 255); + m_BkColor = RGB(255, 255, 255); + m_BkColorSelected = RGB(230, 230, 230); + m_TextColorHc = RGB(255, 255, 255); + m_TextColorSelectedHc = RGB(0, 0, 0); + m_BkColorHc = RGB(0, 0, 0); + m_BkColorSelectedHc = RGB(0, 255, 255); + m_FontHeight = 16; + m_FontRender = CLEARTYPE_NATURAL_QUALITY; + + // Mouse + m_bHover = FALSE; + m_bFocas = FALSE; + m_bTrackingNow = FALSE; + m_bHandCursor = FALSE; +} + +CComboBoxFx::~CComboBoxFx() +{ + m_BkBrush.DeleteObject(); +} + +IMPLEMENT_DYNAMIC(CComboBoxFx, CComboBox) + +BEGIN_MESSAGE_MAP(CComboBoxFx, CComboBox) + //{{AFX_MSG_MAP(CComboBoxFx) + ON_WM_CTLCOLOR() + ON_WM_MOUSEMOVE() + ON_WM_MOUSEHOVER() + ON_WM_MOUSELEAVE() + ON_WM_KILLFOCUS() + ON_WM_SETFOCUS() + ON_WM_SETCURSOR() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +//------------------------------------------------ +// Control +//------------------------------------------------ + +BOOL CComboBoxFx::InitControl(int x, int y, int width, int height, double zoomRatio, CDC* bkDC, + LPCWSTR imagePath, int imageCount, DWORD textAlign, int renderMode, BOOL bHighContrast, BOOL bDarkMode, + COLORREF bkColor, COLORREF bkColorSelected, COLORREF glassColor, BYTE glassAlpha) +{ + m_X = (int)(x * zoomRatio); + m_Y = (int)(y * zoomRatio); + m_ZoomRatio = zoomRatio; + m_CtrlSize.cx = (int)(width * zoomRatio); + m_CtrlSize.cy = (int)(height * zoomRatio); + MoveWindow(m_X, m_Y, m_CtrlSize.cx, m_CtrlSize.cy); + + m_BkDC = bkDC; + m_ImagePath = imagePath; + m_ImageCount = imageCount; + m_RenderMode = renderMode; + + m_BkColor = bkColor; + m_BkColorSelected = bkColorSelected; + m_GlassColor = glassColor; + m_GlassAlpha = glassAlpha; + + // BkBrush + m_BkBrush.DeleteObject(); + if (bDarkMode) + { + m_BkBrush.CreateSolidBrush(RGB(32, 32, 32)); + } + else + { + m_BkBrush.CreateSolidBrush(bkColor); + } + + if (ES_LEFT <= textAlign && textAlign <= ES_RIGHT) + { + m_TextAlign = textAlign; + ModifyStyle(0, m_TextAlign); + } + + if (m_ToolTip.m_hWnd != NULL) + { + if (m_ToolTip.GetToolCount() != 0) + { + m_ToolTip.DelTool(this, 1); + } + CRect rect; + GetClientRect(rect); + m_ToolTip.AddTool(this, m_ToolTipText, rect, 1); + } + + m_bHighContrast = bHighContrast; + m_bDarkMode = bDarkMode; + + if (renderMode & SystemDraw) + { +#if _MSC_VER <= 1310 + if (IsNT3()) + { + ModifyStyle(0, WS_BORDER, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED); + } +#endif + + return TRUE; + } + else + { + m_ImageCount = 1; + m_CtrlImage.Destroy(); + m_CtrlImage.Create(m_CtrlSize.cx, m_CtrlSize.cy * m_ImageCount, 32); + m_CtrlBitmap.Detach(); + m_CtrlBitmap.Attach((HBITMAP)m_CtrlImage); + DWORD length = m_CtrlSize.cx * m_CtrlSize.cy * m_ImageCount * 4; + BYTE* bitmapBits = new BYTE[length]; + m_CtrlBitmap.GetBitmapBits(length, bitmapBits); + + BYTE r, g, b, a; + if (renderMode & OwnerDrawGlass) + { + r = (BYTE)GetRValue(m_GlassColor); + g = (BYTE)GetGValue(m_GlassColor); + b = (BYTE)GetBValue(m_GlassColor); + a = m_GlassAlpha; + } + else // OwnerDrawTransparent + { + r = 0; + g = 0; + b = 0; + a = 0; + } + + for (int y = 0; y < (int)(m_CtrlSize.cy * m_ImageCount); y++) + { + for (int x = 0; x < m_CtrlSize.cx; x++) + { + DWORD p = (y * m_CtrlSize.cx + x) * 4; +#if _MSC_VER > 1310 +#pragma warning( disable : 6386 ) +#endif + bitmapBits[p + 0] = b; + bitmapBits[p + 1] = g; + bitmapBits[p + 2] = r; + bitmapBits[p + 3] = a; +#if _MSC_VER > 1310 +#pragma warning( default : 6386 ) +#endif + } + } + + m_CtrlBitmap.SetBitmapBits(length, bitmapBits); + delete[] bitmapBits; + } + + SetBkReload(); + Invalidate(); + + return TRUE; +} + +void CComboBoxFx::SetFontHeight(int height, double zoomRatio, double fontRatio) +{ + m_FontHeight = (LONG)(-1 * height * zoomRatio * fontRatio); +} + +void CComboBoxFx::SetItemHeightEx(int nIndex, int height, double zoomRatio, double fontRatio) +{ + if (nIndex == -1) + { + CRect rc; + GetWindowRect(&rc); + CComboBox::SetItemHeight(-1, (UINT)(height * zoomRatio - rc.Height() + GetItemHeight(-1))); + } + else + { + CComboBox::SetItemHeight(nIndex, (UINT)(height * zoomRatio * fontRatio)); + } +} + +void CComboBoxFx::SetItemHeightAll(int height, double zoomRatio, double fontRatio) +{ + m_FontHeight = (LONG)(-1 * height * zoomRatio * fontRatio); + + CRect rc; + GetWindowRect(&rc); + CComboBox::SetItemHeight(-1, (UINT)(height * zoomRatio - rc.Height() + GetItemHeight(-1))); + + for(int i = 0; i < this->GetCount(); i++) + { + CComboBox::SetItemHeight(i, (UINT)(height * zoomRatio * fontRatio)); + } +} + +void CComboBoxFx::SetMargin(int top, int left, int bottom, int right, double zoomRatio) +{ + m_Margin.top = (int)(top * zoomRatio); + m_Margin.left = (int)(left * zoomRatio); + m_Margin.bottom = (int)(bottom * zoomRatio); + m_Margin.right = (int)(right * zoomRatio); + m_ZoomRatio = zoomRatio; +} + +CSize CComboBoxFx::GetSize(void) +{ + return m_CtrlSize; +} + +void CComboBoxFx::SetGlassColor(COLORREF glassColor, BYTE glassAlpha) +{ + m_GlassColor = glassColor; + m_GlassAlpha = glassAlpha; +} + +void CComboBoxFx::SetAlpha(BYTE alpha) +{ + m_Alpha = alpha; +} + +HWND CComboBoxFx::GetListHwnd() +{ +#if _MSC_VER > 1310 + COMBOBOXINFO info = { 0 }; + info.cbSize = sizeof(COMBOBOXINFO); + GetComboBoxInfo(&info); + + return info.hwndList; +#else + return NULL; +#endif +} + +HBRUSH CComboBoxFx::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) +{ + HBRUSH hbr = CComboBox::OnCtlColor(pDC, pWnd, nCtlColor); + switch (nCtlColor) { + case CTLCOLOR_EDIT: + pDC->SetBkMode(TRANSPARENT); + return hbr; + case CTLCOLOR_LISTBOX: + pDC->SetBkMode(TRANSPARENT); + return m_BkBrush; + default: + return hbr; + } +} + +//------------------------------------------------ +// Draw Control +//------------------------------------------------ + +void CComboBoxFx::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) +{ + if (lpDrawItemStruct->itemID == -1) { return; } + + static COLORREF textColor; + static COLORREF textColorSelected; + static COLORREF bkColor; + static COLORREF bkColorSelected; + + static int count = 0; + if (m_bHighContrast) + { + textColor = GetTextColor(lpDrawItemStruct->hDC); + textColorSelected = RGB(0, 0, 0); + bkColor = GetBkColor(lpDrawItemStruct->hDC); + bkColorSelected = RGB(0, 255, 255); + + if (bkColor <= RGB(0x80, 0x80, 0x80)) { textColor = RGB(255, 255, 255); } + else { textColor = RGB(0, 0, 0); } + } + else if (m_bDarkMode) + { + textColor = RGB(255, 255, 255); + textColorSelected = RGB(255, 255, 255); + bkColor = RGB(32, 32, 32); + bkColorSelected = RGB(77, 77, 77); + } + else + { + textColor = m_TextColor; + textColorSelected = m_TextColorSelected; + bkColor = m_BkColor; + bkColorSelected = m_BkColorSelected; + } + + CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC); + LoadCtrlBk(pDC); + CString title; + GetLBText(lpDrawItemStruct->itemID, title); + + CBrush Brush; + CBrush* pOldBrush; + if (lpDrawItemStruct->rcItem.left != 0 && !m_bHighContrast) + { + DrawControl(title, pDC, lpDrawItemStruct, m_CtrlBitmap, m_BkBitmap, ControlImageNormal); + Brush.CreateSolidBrush(bkColorSelected); + pOldBrush = pDC->SelectObject(&Brush); + if (lpDrawItemStruct->itemState & ODS_SELECTED) + { + RECT rc = lpDrawItemStruct->rcItem; + // rc.top = (LONG)(rc.bottom - 2 * m_ZoomRatio); + rc.right = (LONG)(rc.left + 3 * m_ZoomRatio); + FillRect(lpDrawItemStruct->hDC, &rc, (HBRUSH)Brush); + } + DrawString(title, pDC, lpDrawItemStruct, textColor); + +#if _MSC_VER <= 1310 + if (IsNT3() && IsWindowEnabled()) + { + DWORD oldTextAlign = m_TextAlign; + m_TextAlign = ES_RIGHT; + DrawString(_T("v"), pDC, lpDrawItemStruct, textColor); + m_TextAlign = oldTextAlign; + } +#endif + } + else + { + if (lpDrawItemStruct->itemState & ODS_SELECTED) + { + Brush.CreateSolidBrush(bkColorSelected); + pOldBrush = pDC->SelectObject(&Brush); + FillRect(lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem, (HBRUSH)Brush); + DrawString(title, pDC, lpDrawItemStruct, textColorSelected); + } + else + { + Brush.CreateSolidBrush(bkColor); + pOldBrush = pDC->SelectObject(&Brush); + FillRect(lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem, (HBRUSH)Brush); + DrawString(title, pDC, lpDrawItemStruct, textColor); + } + } + pDC->SelectObject(pOldBrush); + Brush.DeleteObject(); +} + +void CComboBoxFx::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct) +{ + lpMeasureItemStruct->itemHeight = abs(m_FontHeight); +} + +void CComboBoxFx::DrawControl(CString title, CDC* drawDC, LPDRAWITEMSTRUCT lpDrawItemStruct, CBitmap& ctrlBitmap, CBitmap& bkBitmap, int no) +{ + CDC* pMemDC = new CDC; + CBitmap* pOldMemBitmap; + pMemDC->CreateCompatibleDC(drawDC); + pOldMemBitmap = pMemDC->SelectObject(&ctrlBitmap); + CDC* pBkDC = new CDC; + CBitmap* pOldBkBitmap; + pBkDC->CreateCompatibleDC(drawDC); + pOldBkBitmap = pBkDC->SelectObject(&bkBitmap); + + CBitmap DrawBmp; + DrawBmp.CreateCompatibleBitmap(drawDC, m_CtrlSize.cx, m_CtrlSize.cy); + CDC* pDrawBmpDC = new CDC; + CBitmap* pOldDrawBitmap; + pDrawBmpDC->CreateCompatibleDC(drawDC); + pOldDrawBitmap = pDrawBmpDC->SelectObject(&DrawBmp); + + int color = drawDC->GetDeviceCaps(BITSPIXEL) * drawDC->GetDeviceCaps(PLANES); + + if (!m_CtrlImage.IsNull()) + { + if (m_CtrlImage.GetBPP() == 32) + { + CBitmap* bk32Bitmap; + CImage bk32Image; + if (color == 32) + { + bk32Bitmap = &bkBitmap; + } + else + { + bk32Image.Create(m_CtrlSize.cx, m_CtrlSize.cy, 32); + ::BitBlt(bk32Image.GetDC(), 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, *pBkDC, 0, 0, SRCCOPY); + bk32Bitmap = CBitmap::FromHandle((HBITMAP)bk32Image); + } + + BITMAP CtlBmpInfo, DstBmpInfo; + bk32Bitmap->GetBitmap(&DstBmpInfo); + DWORD DstLineBytes = DstBmpInfo.bmWidthBytes; + DWORD DstMemSize = DstLineBytes * DstBmpInfo.bmHeight; + ctrlBitmap.GetBitmap(&CtlBmpInfo); + DWORD CtlLineBytes = CtlBmpInfo.bmWidthBytes; + DWORD CtlMemSize = CtlLineBytes * CtlBmpInfo.bmHeight; + BYTE* DstBuffer = new BYTE[DstMemSize]; + bk32Bitmap->GetBitmapBits(DstMemSize, DstBuffer); + BYTE* CtlBuffer = new BYTE[CtlMemSize]; + ctrlBitmap.GetBitmapBits(CtlMemSize, CtlBuffer); + + int baseY = m_CtrlSize.cy * no; + for (LONG py = 0; py < DstBmpInfo.bmHeight; py++) + { + int dn = py * DstLineBytes; + int cn = (baseY + py) * CtlLineBytes; + for (LONG px = 0; px < DstBmpInfo.bmWidth; px++) + { +#if _MSC_VER > 1310 +#pragma warning( disable : 6385 ) +#pragma warning( disable : 6386 ) +#endif + BYTE a = CtlBuffer[cn + 3]; + BYTE na = 255 - a; + DstBuffer[dn + 0] = (BYTE)((CtlBuffer[cn + 0] * a + DstBuffer[dn + 0] * na) / 255); + DstBuffer[dn + 1] = (BYTE)((CtlBuffer[cn + 1] * a + DstBuffer[dn + 1] * na) / 255); + DstBuffer[dn + 2] = (BYTE)((CtlBuffer[cn + 2] * a + DstBuffer[dn + 2] * na) / 255); + dn += (DstBmpInfo.bmBitsPixel / 8); + cn += (CtlBmpInfo.bmBitsPixel / 8); +#if _MSC_VER > 1310 +#pragma warning( default : 6386 ) +#pragma warning( default : 6385 ) +#endif + } + } + + if (color == 32) + { + DrawBmp.SetBitmapBits(DstMemSize, DstBuffer); + } + else + { + bk32Bitmap->SetBitmapBits(DstMemSize, DstBuffer); + ::BitBlt(pDrawBmpDC->GetSafeHdc(), 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, bk32Image.GetDC(), 0, 0, SRCCOPY); + bk32Image.ReleaseDC(); + } + drawDC->BitBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, pDrawBmpDC, 0, 0, SRCCOPY); + + delete[] DstBuffer; + delete[] CtlBuffer; + } + else + { + pDrawBmpDC->BitBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, pMemDC, 0, m_CtrlSize.cy * no, SRCCOPY); + drawDC->BitBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, pDrawBmpDC, 0, 0, SRCCOPY); + } + } + else + { + pDrawBmpDC->BitBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, pBkDC, 0, m_CtrlSize.cy * no, SRCCOPY); + drawDC->BitBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, pDrawBmpDC, 0, 0, SRCCOPY); + } + + pDrawBmpDC->SelectObject(&pOldDrawBitmap); + pDrawBmpDC->DeleteDC(); + delete pDrawBmpDC; + pMemDC->SelectObject(&pOldMemBitmap); + pMemDC->DeleteDC(); + delete pMemDC; + pBkDC->SelectObject(&pOldBkBitmap); + pBkDC->DeleteDC(); + delete pBkDC; +} + +void CComboBoxFx::DrawString(CString title, CDC* drawDC, LPDRAWITEMSTRUCT lpDrawItemStruct, COLORREF textColor) +{ + if (title.IsEmpty()) + { + return; + } + + drawDC->SetBkMode(TRANSPARENT); + CRect rect = (CRect)(lpDrawItemStruct->rcItem); + rect.top += m_Margin.top; + rect.left += m_Margin.left; + rect.bottom -= m_Margin.bottom; + rect.right -= m_Margin.right; + drawDC->SetTextColor(textColor); + + if (m_TextAlign == ES_LEFT) + { + drawDC->DrawText(title, title.GetLength(), rect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + } + else if (m_TextAlign == ES_RIGHT) + { + drawDC->DrawText(title, title.GetLength(), rect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); + } + else + { + drawDC->DrawText(title, title.GetLength(), rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + } +} + +//------------------------------------------------ +// Image +//------------------------------------------------ + +BOOL CComboBoxFx::LoadBitmap(LPCTSTR fileName) +{ + if (m_bHighContrast) { return FALSE; } + if (fileName == NULL) { return FALSE; } + + m_CtrlImage.Destroy(); + m_CtrlImage.Load(fileName); + if (m_CtrlImage.IsNull()) { return FALSE; } + + return LoadBitmap((HBITMAP)m_CtrlImage); +} + +BOOL CComboBoxFx::LoadBitmap(HBITMAP hBitmap) +{ + if (m_bHighContrast) { return FALSE; } + + m_CtrlBitmap.Detach(); + m_CtrlBitmap.Attach(hBitmap); + + return SetBitmap(m_CtrlBitmap); +} + +void CComboBoxFx::SetBkReload(void) +{ + m_bBkBitmapInit = FALSE; + m_bBkLoad = FALSE; +} + +BOOL CComboBoxFx::SetBitmap(CBitmap& bitmap) +{ + if (m_bHighContrast) { return FALSE; } + + BITMAP bitmapinfo; + bitmap.GetBitmap(&bitmapinfo); + + if (m_CtrlSize.cx != bitmapinfo.bmWidth + || m_CtrlSize.cy != bitmapinfo.bmHeight / m_ImageCount) + { + return FALSE; + } + else + { + return TRUE; + } +} + +void CComboBoxFx::LoadCtrlBk(CDC* drawDC) +{ + if (m_bHighContrast) { SetBkReload(); return; } + + if (m_BkBitmap.m_hObject != NULL) + { + BITMAP bitmapInfo; + m_BkBitmap.GetBitmap(&bitmapInfo); + if (bitmapInfo.bmBitsPixel != drawDC->GetDeviceCaps(BITSPIXEL)) + { + SetBkReload(); + } + } + + if (&m_CtrlBitmap != NULL) + { + if (!m_bBkBitmapInit) + { + m_BkBitmap.DeleteObject(); + m_BkBitmap.CreateCompatibleBitmap(drawDC, m_CtrlSize.cx, m_CtrlSize.cy); + m_bBkBitmapInit = TRUE; + } + + if (!m_bBkLoad) + { + CBitmap* pOldBitmap; + CDC* pMemDC = new CDC; + pMemDC->CreateCompatibleDC(drawDC); + pOldBitmap = pMemDC->SelectObject(&m_BkBitmap); + pMemDC->BitBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, m_BkDC, m_X, m_Y, SRCCOPY); + pMemDC->SelectObject(pOldBitmap); + pMemDC->DeleteDC(); + delete pMemDC; + m_bBkLoad = TRUE; + } + } +} + +//------------------------------------------------ +// Font +//------------------------------------------------ + +void CComboBoxFx::SetFontEx(CString face, int size, int sizeToolTip, double zoomRatio, double fontRatio, + COLORREF textColor, COLORREF textColorSelected, LONG fontWeight, BYTE fontRender) +{ + LOGFONT logFont = { 0 }; + logFont.lfCharSet = DEFAULT_CHARSET; + logFont.lfHeight = (LONG)(-1 * size * zoomRatio * fontRatio); + logFont.lfQuality = fontRender; + logFont.lfWeight = fontWeight; + m_FontRender = fontRender; + + if (face.GetLength() < 32) + { + wsprintf(logFont.lfFaceName, _T("%s"), (LPCTSTR)face); + } + else + { + wsprintf(logFont.lfFaceName, _T("")); + } + + m_Font.DeleteObject(); + m_Font.CreateFontIndirect(&logFont); + SetFont(&m_Font); + + logFont.lfHeight = (LONG)(-1 * sizeToolTip * zoomRatio); + m_FontToolTip.DeleteObject(); + m_FontToolTip.CreateFontIndirect(&logFont); + + if (! m_bHighContrast) + { + m_TextColor = textColor; + m_TextColorSelected = textColorSelected; + } + + if (m_ToolTip.m_hWnd != NULL) + { + m_ToolTip.SetFont(&m_FontToolTip); + } +} + +//------------------------------------------------ +// Mouse +//------------------------------------------------ + +void CComboBoxFx::SetHandCursor(BOOL bHandCuror) +{ + m_bHandCursor = bHandCuror; +} + +void CComboBoxFx::OnMouseMove(UINT nFlags, CPoint point) +{ +#if _MSC_VER <= 1310 + typedef BOOL(WINAPI* Func_TrackMouseEvent)(LPTRACKMOUSEEVENT); + static Func_TrackMouseEvent p_TrackMouseEvent = NULL; + static BOOL bInit_TrackMouseEvent = FALSE; + + if (bInit_TrackMouseEvent && p_TrackMouseEvent == NULL) + { + return; // TrackMouseEvent is not available + } + else + { + HMODULE hModule = GetModuleHandle(_T("user32.dll")); + if (hModule) + { + p_TrackMouseEvent = (Func_TrackMouseEvent)GetProcAddress(hModule, "TrackMouseEvent"); + } + } + + if (p_TrackMouseEvent != NULL) + { + TRACKMOUSEEVENT tme = { sizeof(TRACKMOUSEEVENT) }; + tme.hwndTrack = m_hWnd; + tme.dwFlags = TME_LEAVE | TME_HOVER; + tme.dwHoverTime = 1; + m_bTrackingNow = p_TrackMouseEvent(&tme); + } + bInit_TrackMouseEvent = TRUE; +#else + if (!m_bTrackingNow) + { + TRACKMOUSEEVENT tme = { sizeof(TRACKMOUSEEVENT) }; + tme.hwndTrack = m_hWnd; + tme.dwFlags = TME_LEAVE | TME_HOVER; + tme.dwHoverTime = 1; + m_bTrackingNow = _TrackMouseEvent(&tme); + } +#endif + + CComboBox::OnMouseMove(nFlags, point); +} + +void CComboBoxFx::OnMouseHover(UINT nFlags, CPoint point) +{ +#if _MSC_VER > 1310 + CComboBox::OnMouseHover(nFlags, point); +#endif + m_bHover = TRUE; + Invalidate(); +} + +void CComboBoxFx::OnMouseLeave() +{ +#if _MSC_VER > 1310 + CComboBox::OnMouseLeave(); +#endif + m_bTrackingNow = FALSE; + m_bHover = FALSE; + Invalidate(); +} + +void CComboBoxFx::OnSetfocus() +{ + m_bFocas = TRUE; + Invalidate(); +} + +void CComboBoxFx::OnKillfocus() +{ + m_bFocas = FALSE; + Invalidate(); +} + +BOOL CComboBoxFx::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) +{ + HCURSOR hCursor = NULL; + if (m_bHandCursor) + { + hCursor = AfxGetApp()->LoadStandardCursor(IDC_HAND); + if (hCursor) + { + ::SetCursor(hCursor); + } + } + else + { + hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW); + if (hCursor) + { + ::SetCursor(hCursor); + } + } + + return TRUE; +} + +//------------------------------------------------ +// ToolTip +//------------------------------------------------ + +void CComboBoxFx::SetToolTipText(LPCTSTR text) +{ + if (text == NULL) { return; } + + InitToolTip(); + m_ToolTipText = text; + + if (m_ToolTip.GetToolCount() == 0) + { + CRect rect; + GetClientRect(rect); + m_ToolTip.AddTool(this, m_ToolTipText, rect, 1); + } + else + { + m_ToolTip.UpdateTipText(m_ToolTipText, this, 1); + } + + SetToolTipActivate(TRUE); +} + +void CComboBoxFx::SetToolTipActivate(BOOL bActivate) +{ + if (m_ToolTip.GetToolCount() == 0) { return; } + m_ToolTip.Activate(bActivate); +} + +void CComboBoxFx::SetToolTipWindowText(LPCTSTR pText) +{ + SetToolTipText(pText); + SetWindowText(pText); +} + +CString CComboBoxFx::GetToolTipText() +{ + return m_ToolTipText; +} + +void CComboBoxFx::InitToolTip() +{ + if (m_ToolTip.m_hWnd == NULL) + { + m_ToolTip.Create(this, TTS_ALWAYSTIP | TTS_BALLOON | TTS_NOANIMATE | TTS_NOFADE); + m_ToolTip.Activate(FALSE); + m_ToolTip.SetFont(&m_FontToolTip); + m_ToolTip.SendMessage(TTM_SETMAXTIPWIDTH, 0, 1024); + m_ToolTip.SetDelayTime(TTDT_AUTOPOP, 8000); + m_ToolTip.SetDelayTime(TTDT_INITIAL, 500); + m_ToolTip.SetDelayTime(TTDT_RESHOW, 100); + } +} + +BOOL CComboBoxFx::PreTranslateMessage(MSG* pMsg) +{ + InitToolTip(); + m_ToolTip.RelayEvent(pMsg); + + return CComboBox::PreTranslateMessage(pMsg); +} \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/ComboBoxFx.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ComboBoxFx.h new file mode 100644 index 0000000..fb14ddd --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ComboBoxFx.h @@ -0,0 +1,130 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once + +#include "ImageFx.h" + +class CComboBoxFx : public CComboBox +{ + DECLARE_DYNAMIC(CComboBoxFx); + +// Constructors +public: + CComboBoxFx(); + virtual ~CComboBoxFx(); + +// Control +public: + BOOL InitControl(int x, int y, int width, int height, double zoomRatio, CDC* bkDC, + LPCWSTR imagePath, int imageCount, DWORD textAlign, int renderMode, BOOL bHighContrast, BOOL m_bDarkMode, + COLORREF bkColor, COLORREF bkColorSelected, COLORREF glassColor, BYTE glassAlpha + ); + void SetFontHeight(int height, double zoomRatio, double fontRatio = 1.0); + void SetItemHeightEx(int nIndex, int height, double zoomRatio, double fontRatio = 1.0); + void SetItemHeightAll(int height, double zoomRatio, double fontRatio = 1.0); + void SetMargin(int top, int left, int bottom, int right, double zoomRatio); + CSize GetSize(void); + void SetGlassColor(COLORREF glassColor, BYTE glassAlpha); + void SetAlpha(BYTE alpha); + HWND GetListHwnd(); + + // Font + void SetFontEx(CString face, int size, int sizeToolTip, double zoomRatio, double fontRatio = 1.0, + COLORREF textColor = RGB(0, 0, 0), COLORREF textColorSelected = RGB(0, 0, 0), LONG fontWeight = FW_NORMAL, BYTE fontRender = CLEARTYPE_NATURAL_QUALITY); + + // ToolTip + void SetToolTipText(LPCTSTR pText); + void SetToolTipActivate(BOOL bActivate = TRUE); + void SetToolTipWindowText(LPCTSTR pText); + CString GetToolTipText(); + + // Mouse + void SetHandCursor(BOOL bHandCuror = TRUE); + +protected: + // Draw Control + virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); + virtual void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct); + virtual void DrawControl(CString title, CDC* drawDC, LPDRAWITEMSTRUCT lpDrawItemStruct, CBitmap& ctrlBitmap, CBitmap& bkBitmap, int no); + virtual void DrawString(CString title, CDC* drawDC, LPDRAWITEMSTRUCT lpDrawItemStruct, COLORREF textColor); + + // Image + BOOL LoadBitmap(LPCTSTR fileName); + BOOL LoadBitmap(HBITMAP hBitmap); + void SetBkReload(void); + BOOL SetBitmap(CBitmap& bitmap); + void LoadCtrlBk(CDC* drawDC); + + // ToolTip + void InitToolTip(); + virtual BOOL PreTranslateMessage(MSG* pMsg); + + // Message Map + DECLARE_MESSAGE_MAP() + afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor); + afx_msg void OnMouseMove(UINT nFlags, CPoint point); + afx_msg void OnMouseHover(UINT nFlags, CPoint point); + afx_msg void OnMouseLeave(); + afx_msg void OnKillfocus(); + afx_msg void OnSetfocus(); + afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message); + +protected: + // Control + int m_X; + int m_Y; + double m_ZoomRatio; + CSize m_CtrlSize; + CRect m_Margin; + int m_RenderMode; + BOOL m_bHighContrast; + BOOL m_bDarkMode; + BYTE m_FontRender; // For FontComboBoxFx + + // Alpha/Glass + BYTE m_Alpha; + COLORREF m_GlassColor; + BYTE m_GlassAlpha; + + // Image + CString m_ImagePath; + int m_ImageCount; + CDC* m_BkDC; + CBitmap m_BkBitmap; + BOOL m_bBkBitmapInit; + BOOL m_bBkLoad; + CBitmap m_CtrlBitmap; + CImage m_CtrlImage; + + // Font + DWORD m_TextAlign; + CFont m_Font; + CFont m_FontToolTip; + COLORREF m_TextColor; + COLORREF m_TextColorSelected; + COLORREF m_BkColor; + COLORREF m_BkColorSelected; + COLORREF m_TextColorHc; + COLORREF m_TextColorSelectedHc; + COLORREF m_BkColorHc; + COLORREF m_BkColorSelectedHc; + LONG m_FontHeight; + + // ToolTip + CToolTipCtrl m_ToolTip; + CString m_ToolTipText; + + // Mouse + BOOL m_bHover; + BOOL m_bFocas; + BOOL m_bTrackingNow; + BOOL m_bHandCursor; + + // Brush + CBrush m_BkBrush; +}; diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/CommonFx.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/CommonFx.h new file mode 100644 index 0000000..0dd11f3 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/CommonFx.h @@ -0,0 +1,89 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once + +//------------------------------------------------ +// Naming Conventions +//------------------------------------------------ +// BOOL bXxxxYyyy +// HANDLE hXxxxYyyy +// Pointer pXxxxYyyy +// Function SampleFunction +// Variable sampleVariable +// Const Value ConstVaiable +// Member Variable m_XxxxYyyy + +//------------------------------------------------ +// Order for C*****Fx Control +//------------------------------------------------ +// Control > Draw Control > Image > Font > Mouse > ToolTip +// + +//------------------------------------------------ +// Utility Macros +//------------------------------------------------ + +#define SAFE_DELETE(p) {if(p){delete (p);(p)=NULL;}} + +#if _MSC_VER > 1310 +#define MENU_MODIFY_MENU menu->ModifyMenu +#define SUBMENU_MODIFY_MENU subMenu.ModifyMenu +#else +#define MENU_MODIFY_MENU if(!IsNT3())menu->ModifyMenu +#define SUBMENU_MODIFY_MENU if(!IsNT3())subMenu.ModifyMenu +#endif + +//------------------------------------------------ +// WM_APP +//------------------------------------------------ +// WM_APP + 0x0000-0x0BFF: User Application +// WM_APP + 0x0C00-0x0FFF: Project Priscilla + // WM_APP + 0x0C00-0x0CFF: Theme + // WM_APP + 0x0D00-0x0DFF: Language + // WP_APP + 0x0E00-0x0FFF: Reserved +// WM_APP + 0x1000-0x3FFF: User Application + +#define WM_THEME_ID (WM_APP + 0x0C00) +#define WM_LANGUAGE_ID (WM_APP + 0x0D00) + +//------------------------------------------------ +// TIMER ID +//------------------------------------------------ +// 0x0000 - 0x0FFF: Project Priscilla +// 0x1000 - : User Application + +static const int TimerUpdateDialogSizeDpiChanged = 0x0001; +static const int TimerUpdateDialogSizeDisplayChange = 0x0002; +static const int TimerUpdateDialogSizeSysColorChange = 0x0003; +static const int TimerUpdateDialogSizeSettingChange = 0x0004; + +//------------------------------------------------ +// Const Values +//------------------------------------------------ + +static const int ControlImageNormal = 0x0000; +static const int ControlImageHover = 0x0001; +static const int ControlImageFocus = 0x0002; +static const int ControlImageSelected = 0x0003; +static const int ControlImageDisabled = 0x0004; + +static const int SystemDraw = 0x0001; +static const int OwnerDrawImage = 0x0002; +static const int OwnerDrawGlass = 0x0004; +static const int OwnerDrawTransparent = 0x0008; + +static const int ZoomTypeAuto = 0; +static const int ZoomType050 = 50; +static const int ZoomType064 = 64; +static const int ZoomType075 = 75; +static const int ZoomType100 = 100; +static const int ZoomType125 = 125; +static const int ZoomType150 = 150; +static const int ZoomType200 = 200; +static const int ZoomType250 = 250; +static const int ZoomType300 = 300; \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/DarkMode.cpp b/CristalDiskMark/source/CrystalDiskMark/Priscilla/DarkMode.cpp new file mode 100644 index 0000000..aca5b2c --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/DarkMode.cpp @@ -0,0 +1,261 @@ +/*---------------------------------------------------------------------------*/ +// Author : Richard Yu +// Web : https://github.com/ysc3839/win32-darkmode +// License : MIT License +// https://github.com/ysc3839/win32-darkmode/blob/master/LICENSE +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "OsInfoFx.h" +#include "IatHook.h" + +enum IMMERSIVE_HC_CACHE_MODE +{ + IHCM_USE_CACHED_VALUE, + IHCM_REFRESH +}; + +// 1903 18362 +enum class PreferredAppMode +{ + Default, + AllowDark, + ForceDark, + ForceLight, + Max +}; + +enum WINDOWCOMPOSITIONATTRIB +{ + WCA_UNDEFINED = 0, + WCA_NCRENDERING_ENABLED = 1, + WCA_NCRENDERING_POLICY = 2, + WCA_TRANSITIONS_FORCEDISABLED = 3, + WCA_ALLOW_NCPAINT = 4, + WCA_CAPTION_BUTTON_BOUNDS = 5, + WCA_NONCLIENT_RTL_LAYOUT = 6, + WCA_FORCE_ICONIC_REPRESENTATION = 7, + WCA_EXTENDED_FRAME_BOUNDS = 8, + WCA_HAS_ICONIC_BITMAP = 9, + WCA_THEME_ATTRIBUTES = 10, + WCA_NCRENDERING_EXILED = 11, + WCA_NCADORNMENTINFO = 12, + WCA_EXCLUDED_FROM_LIVEPREVIEW = 13, + WCA_VIDEO_OVERLAY_ACTIVE = 14, + WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15, + WCA_DISALLOW_PEEK = 16, + WCA_CLOAK = 17, + WCA_CLOAKED = 18, + WCA_ACCENT_POLICY = 19, + WCA_FREEZE_REPRESENTATION = 20, + WCA_EVER_UNCLOAKED = 21, + WCA_VISUAL_OWNER = 22, + WCA_HOLOGRAPHIC = 23, + WCA_EXCLUDED_FROM_DDA = 24, + WCA_PASSIVEUPDATEMODE = 25, + WCA_USEDARKMODECOLORS = 26, + WCA_LAST = 27 +}; + +struct WINDOWCOMPOSITIONATTRIBDATA +{ + WINDOWCOMPOSITIONATTRIB Attrib; + PVOID pvData; + SIZE_T cbData; +}; + +using fnRtlGetNtVersionNumbers = void (WINAPI*)(LPDWORD major, LPDWORD minor, LPDWORD build); +using fnSetWindowCompositionAttribute = BOOL(WINAPI*)(HWND hWnd, WINDOWCOMPOSITIONATTRIBDATA*); +// 1809 17763 +using fnShouldAppsUseDarkMode = bool (WINAPI*)(); // ordinal 132 +using fnAllowDarkModeForWindow = bool (WINAPI*)(HWND hWnd, bool allow); // ordinal 133 +using fnAllowDarkModeForApp = bool (WINAPI*)(bool allow); // ordinal 135, in 1809 +using fnFlushMenuThemes = void (WINAPI*)(); // ordinal 136 +using fnRefreshImmersiveColorPolicyState = void (WINAPI*)(); // ordinal 104 +using fnIsDarkModeAllowedForWindow = bool (WINAPI*)(HWND hWnd); // ordinal 137 +using fnGetIsImmersiveColorUsingHighContrast = bool (WINAPI*)(IMMERSIVE_HC_CACHE_MODE mode); // ordinal 106 +using fnOpenNcThemeData = HTHEME(WINAPI*)(HWND hWnd, LPCWSTR pszClassList); // ordinal 49 +// 1903 18362 +using fnShouldSystemUseDarkMode = bool (WINAPI*)(); // ordinal 138 +using fnSetPreferredAppMode = PreferredAppMode(WINAPI*)(PreferredAppMode appMode); // ordinal 135, in 1903 +using fnIsDarkModeAllowedForApp = bool (WINAPI*)(); // ordinal 139 + +fnSetWindowCompositionAttribute _SetWindowCompositionAttribute = nullptr; +fnShouldAppsUseDarkMode _ShouldAppsUseDarkMode = nullptr; +fnAllowDarkModeForWindow _AllowDarkModeForWindow = nullptr; +fnAllowDarkModeForApp _AllowDarkModeForApp = nullptr; +fnFlushMenuThemes _FlushMenuThemes = nullptr; +fnRefreshImmersiveColorPolicyState _RefreshImmersiveColorPolicyState = nullptr; +fnIsDarkModeAllowedForWindow _IsDarkModeAllowedForWindow = nullptr; +fnGetIsImmersiveColorUsingHighContrast _GetIsImmersiveColorUsingHighContrast = nullptr; +fnOpenNcThemeData _OpenNcThemeData = nullptr; +// 1903 18362 +fnShouldSystemUseDarkMode _ShouldSystemUseDarkMode = nullptr; +fnSetPreferredAppMode _SetPreferredAppMode = nullptr; + +bool g_darkModeSupported = false; +bool g_darkModeEnabled = false; +DWORD g_buildNumber = 0; + +bool AllowDarkModeForWindow(HWND hWnd, bool allow) +{ + if (g_darkModeSupported) + return _AllowDarkModeForWindow(hWnd, allow); + return false; +} + +bool IsHighContrast() +{ + HIGHCONTRASTW highContrast = { sizeof(highContrast) }; + if (SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(highContrast), &highContrast, FALSE)) + return highContrast.dwFlags & HCF_HIGHCONTRASTON; + return false; +} + +void AllowDarkModeForApp(bool allow) +{ + if (_AllowDarkModeForApp) + _AllowDarkModeForApp(allow); + else if (_SetPreferredAppMode) + _SetPreferredAppMode(allow ? PreferredAppMode::AllowDark : PreferredAppMode::Default); +} + +void RefreshTitleBarThemeColor(HWND hWnd) +{ + BOOL dark = FALSE; + if (_IsDarkModeAllowedForWindow(hWnd) && + _ShouldAppsUseDarkMode() && + !IsHighContrast()) + { + dark = TRUE; + } + if (g_buildNumber < 18362) + SetPropW(hWnd, L"UseImmersiveDarkModeColors", reinterpret_cast(static_cast(dark))); + else if (_SetWindowCompositionAttribute) + { + WINDOWCOMPOSITIONATTRIBDATA data = { WCA_USEDARKMODECOLORS, &dark, sizeof(dark) }; + _SetWindowCompositionAttribute(hWnd, &data); + } +} + +constexpr bool CheckBuildNumber(DWORD buildNumber) +{ + return (buildNumber >= 17763); +} + +void FixDarkScrollBar() +{ + HMODULE hComctl = LoadLibraryExW(L"comctl32.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (hComctl) + { + auto addr = FindDelayLoadThunkInModule(hComctl, "uxtheme.dll", 49); // OpenNcThemeData + if (addr) + { + DWORD oldProtect; + if (VirtualProtect(addr, sizeof(IMAGE_THUNK_DATA), PAGE_READWRITE, &oldProtect)) + { + auto MyOpenThemeData = [](HWND hWnd, LPCWSTR classList) -> HTHEME { + if (wcscmp(classList, L"ScrollBar") == 0) + { + hWnd = nullptr; + classList = L"Explorer::ScrollBar"; + } + return _OpenNcThemeData(hWnd, classList); + }; + + addr->u1.Function = reinterpret_cast(static_cast(MyOpenThemeData)); + VirtualProtect(addr, sizeof(IMAGE_THUNK_DATA), oldProtect, &oldProtect); + } + } + } +} + +BOOL InitDarkMode() +{ + HMODULE ntdll = GetModuleHandleW(L"ntdll.dll"); + HMODULE user32 = GetModuleHandleW(L"user32.dll"); + if (!ntdll || !user32) return FALSE; + auto RtlGetNtVersionNumbers = reinterpret_cast(GetProcAddress(ntdll, "RtlGetNtVersionNumbers")); + if (RtlGetNtVersionNumbers) + { + DWORD major, minor; + RtlGetNtVersionNumbers(&major, &minor, &g_buildNumber); + g_buildNumber &= ~0xF0000000; + if (major == 10 && minor == 0 && CheckBuildNumber(g_buildNumber)) + { + HMODULE hUxtheme = LoadLibraryExW(L"uxtheme.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (hUxtheme) + { + _OpenNcThemeData = reinterpret_cast(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(49))); + _RefreshImmersiveColorPolicyState = reinterpret_cast(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(104))); + _GetIsImmersiveColorUsingHighContrast = reinterpret_cast(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(106))); + _ShouldAppsUseDarkMode = reinterpret_cast(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(132))); + _AllowDarkModeForWindow = reinterpret_cast(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(133))); + + auto ord135 = GetProcAddress(hUxtheme, MAKEINTRESOURCEA(135)); + if (g_buildNumber < 18362) + _AllowDarkModeForApp = reinterpret_cast(ord135); + else + _SetPreferredAppMode = reinterpret_cast(ord135); + + //_FlushMenuThemes = reinterpret_cast(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(136))); + _IsDarkModeAllowedForWindow = reinterpret_cast(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(137))); + + _SetWindowCompositionAttribute = reinterpret_cast(GetProcAddress(user32, "SetWindowCompositionAttribute")); + + if (_OpenNcThemeData && + _RefreshImmersiveColorPolicyState && + _ShouldAppsUseDarkMode && + _AllowDarkModeForWindow && + (_AllowDarkModeForApp || _SetPreferredAppMode) && + //_FlushMenuThemes && + _IsDarkModeAllowedForWindow) + { + g_darkModeSupported = true; + + AllowDarkModeForApp(true); + _RefreshImmersiveColorPolicyState(); + + g_darkModeEnabled = _ShouldAppsUseDarkMode() && !IsHighContrast(); + + // FixDarkScrollBar(); + } + } + } + } + + return (BOOL)g_darkModeEnabled; +} + +BOOL SetDarkMode(HWND hWnd) +{ + BOOL bDarkMode = FALSE; + if (IsDarkModeSupport()) + { + bDarkMode = InitDarkMode(); + AllowDarkModeForWindow(hWnd, bDarkMode); + RefreshTitleBarThemeColor(hWnd); + } + + return bDarkMode; +} + +void UnsetDarkMode(HWND hWnd) +{ + if (IsDarkModeSupport()) + { + InitDarkMode(); + AllowDarkModeForWindow(hWnd, FALSE); + RefreshTitleBarThemeColor(hWnd); + } +} + +void SetDarkModeControl(HWND hWnd, BOOL bDarkMode) +{ + if (IsDarkModeSupport()) + { + SetWindowTheme(hWnd, L"Explorer", nullptr); + AllowDarkModeForWindow(hWnd, bDarkMode); + SendMessageW(hWnd, WM_THEMECHANGED, 0, 0); + } +} \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/DarkMode.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/DarkMode.h new file mode 100644 index 0000000..46d8daf --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/DarkMode.h @@ -0,0 +1,18 @@ +/*---------------------------------------------------------------------------*/ +// Author : Richard Yu +// Web : https://github.com/ysc3839/win32-darkmode +// License : MIT License +// https://github.com/ysc3839/win32-darkmode/blob/master/LICENSE +/*---------------------------------------------------------------------------*/ + +#pragma once + +BOOL SetDarkMode(HWND hWnd); +void UnsetDarkMode(HWND hWnd); +void SetDarkModeControl(HWND hWnd, BOOL bDarkMode); + +void FixDarkScrollBar(); +bool AllowDarkModeForWindow(HWND hWnd, bool allow); + +// BOOL InitDarkMode(); +// void RefreshTitleBarThemeColor(HWND hWnd); diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/DialogFx.cpp b/CristalDiskMark/source/CrystalDiskMark/Priscilla/DialogFx.cpp new file mode 100644 index 0000000..72add00 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/DialogFx.cpp @@ -0,0 +1,738 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "Resource.h" +#include "DialogFx.h" +#include "UtilityFx.h" +#include "OsInfoFx.h" +#include +#include "DarkMode.h" + +// defined by Windows 8.1/Windows 2012 R2 +#ifndef WM_DPICHANGED +#define WM_DPICHANGED 0x02E0 +#endif + +////------------------------------------------------ +// CDialogFx +////------------------------------------------------ + +CDialogFx::CDialogFx(UINT dlgResouce, CWnd* pParent) + :CDialog(dlgResouce, pParent) +{ + // Dialog + m_bInitializing = TRUE; + m_bDpiChanging = FALSE; + m_bShowWindow = FALSE; + m_bModelessDlg = FALSE; + m_bHighContrast = FALSE; + m_bDarkMode = FALSE; + m_bDisableDarkMode = FALSE; + m_bBkImage = FALSE; + m_MenuId = 0; + m_ParentWnd = NULL; + m_DlgWnd = NULL; + m_hAccelerator = NULL; + m_bDrag = FALSE; + m_FontScale = 100; + m_FontRatio = 1.0; + m_FontRender = CLEARTYPE_NATURAL_QUALITY; + m_hPal = NULL; + + m_SizeX = 0; + m_MaxSizeX = 65535; + m_MinSizeX = 0; + m_SizeY = 0; + m_MaxSizeY = 65535; + m_MinSizeY = 0; + + // Zoom + m_Dpi = 96; + m_ZoomRatio = 1.0; + m_ZoomType = ZoomTypeAuto; + + // Color for SubClass + m_LabelText = 0x00000000; + m_MeterText = 0x00000000; + m_ComboText = 0x00000000; + m_ComboTextSelected = 0x00808080; + m_ComboBk = 0x00FFFFFF; + m_ComboBkSelected = 0x00808080; + m_ButtonText = 0x00000000; + m_EditText = 0x00000000; + m_EditBk = 0x00FFFFFF; + m_ListText1 = 0x00000000; + m_ListText2 = 0x00000000; + m_ListTextSelected = 0x00000000; + m_ListBk1 = 0x00FFFFFF; + m_ListBk2 = 0x00FFFFFF; + m_ListBkSelected = 0x00808080; + m_ListLine1 = 0x00000000; + m_ListLine2 = 0x00000000; + m_Glass = 0x00808080; + m_Frame = 0x00808080; + m_Background = 0xFFFFFFFF; // Disabled + + m_ComboAlpha = 0; + m_EditAlpha = 0; + m_GlassAlpha = 0; + + m_CharacterPosition = 0; + + // Theme for SubClass + m_OffsetX = 0; + + // Voice for SubClass + m_VoiceVolume = 0; +} + +CDialogFx::~CDialogFx() +{ + if(m_hPal) DeleteObject(m_hPal); +} + +BEGIN_MESSAGE_MAP(CDialogFx, CDialog) + ON_WM_SIZE() + ON_WM_TIMER() + ON_WM_ERASEBKGND() + ON_MESSAGE(WM_DPICHANGED, &CDialogFx::OnDpiChanged) + ON_MESSAGE(WM_DISPLAYCHANGE, &CDialogFx::OnDisplayChange) + ON_MESSAGE(WM_SYSCOLORCHANGE, &CDialogFx::OnSysColorChange) + ON_MESSAGE(WM_SETTINGCHANGE, &CDialogFx::OnSettingChange) + ON_MESSAGE(WM_ENTERSIZEMOVE, &CDialogFx::OnEnterSizeMove) + ON_MESSAGE(WM_EXITSIZEMOVE, &CDialogFx::OnExitSizeMove) +END_MESSAGE_MAP() + +//------------------------------------------------ +// Dialog +//------------------------------------------------ + +BOOL CDialogFx::Create(UINT nIDTemplate, CWnd* pDlgWnd, UINT menuId, CWnd* pParentWnd) +{ + m_bModelessDlg = TRUE; + m_ParentWnd = pParentWnd; + m_DlgWnd = pDlgWnd; + m_MenuId = menuId; + + if (m_MenuId != 0 && m_ParentWnd != NULL) + { + CMenu* menu = m_ParentWnd->GetMenu(); + menu->EnableMenuItem(m_MenuId, MF_GRAYED); + m_ParentWnd->SetMenu(menu); + m_ParentWnd->DrawMenuBar(); + } + + return CDialog::Create(nIDTemplate, pParentWnd); +} + +int CDialogFx::GetDpi() +{ + INT dpi = 96; + CDC* pDC = GetDC(); + dpi = GetDeviceCaps(pDC->m_hDC, LOGPIXELSY); + ReleaseDC(pDC); + + HMODULE hModule = GetModuleHandle(_T("Shcore.dll")); + if (hModule) + { + typedef HRESULT(WINAPI* FuncGetDpiForMonitor) (HMONITOR hmonitor, UINT dpiType, UINT* dpiX, UINT* dpiY); + typedef HMONITOR(WINAPI* FuncMonitorFromWindow) (HWND hwnd, DWORD dwFlags); + + FuncGetDpiForMonitor pGetDpiForMonitor = (FuncGetDpiForMonitor)GetProcAddress(hModule, "GetDpiForMonitor"); + FuncMonitorFromWindow pMonitorFromWindow = (FuncMonitorFromWindow)GetProcAddress(hModule, "MonitorFromWindow"); + + if (pGetDpiForMonitor && pMonitorFromWindow) + { + UINT dpiX, dpiY; + pGetDpiForMonitor(pMonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST), 0, &dpiX, &dpiY); + dpi = dpiY; + } + } + + return dpi; +} + +BOOL CDialogFx::OnInitDialog() +{ + CDialog::OnInitDialog(); + + m_bHighContrast = IsHighContrast(); + m_Dpi = GetDpi(); + m_hAccelerator = ::LoadAccelerators(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_ACCELERATOR)); + + // m_bInitializing = FALSE; + + return TRUE; +} + +void CDialogFx::OnSize(UINT nType, int cx, int cy) +{ + CDialog::OnSize(nType, cx, cy); + + if (nType == SIZE_RESTORED) + { + UpdateBackground(TRUE, FALSE); + Invalidate(); + } +} + +BOOL CDialogFx::PreTranslateMessage(MSG* pMsg) +{ + if(m_hAccelerator != NULL) + { + if(::TranslateAccelerator(m_hWnd, m_hAccelerator, pMsg) != 0) + { + return TRUE; + } + } + + return CDialog::PreTranslateMessage(pMsg); +} + +void CDialogFx::PostNcDestroy() +{ + if (m_bModelessDlg) + { + m_DlgWnd = NULL; + delete this; + } + else + { + CDialog::PostNcDestroy(); + } +} + +void CDialogFx::UpdateDialogSize() +{ +#if _MSC_VER > 1310 + if (! m_bDisableDarkMode) + { + m_bDarkMode = SetDarkMode(m_hWnd); + } + else + { + UnsetDarkMode(m_hWnd); + m_bDarkMode = FALSE; + } +#endif +} + +void CDialogFx::SetClientSize(int sizeX, int sizeY, double zoomRatio) +{ + RECT rw, rc; + GetWindowRect(&rw); + GetClientRect(&rc); + + if (rc.right != 0) + { + int ncaWidth = (rw.right - rw.left) - (rc.right - rc.left); + int ncaHeight = (rw.bottom - rw.top) - (rc.bottom - rc.top); + + SetWindowPos(NULL, 0, 0, (int)(sizeX * zoomRatio) + ncaWidth, (int)(sizeY * zoomRatio) + ncaHeight, SWP_NOMOVE | SWP_NOZORDER); + + GetWindowRect(&rw); + GetClientRect(&rc); + + int ncaHeightMenu = (rw.bottom - rw.top) - (rc.bottom - rc.top); + + if (ncaHeight != ncaHeightMenu) + { + SetWindowPos(NULL, 0, 0, (int)(sizeX * zoomRatio) + ncaWidth, (int)(sizeY * zoomRatio) + ncaHeightMenu, SWP_NOMOVE | SWP_NOZORDER); + } + } +} + +void CDialogFx::UpdateBackground(BOOL resize, BOOL bDarkMode) +{ + BOOL result = FALSE; + CImage srcBitmap; + double ratio = m_ZoomRatio; + m_bBkImage = FALSE; + +#if _MSC_VER > 1310 + if (resize) { m_ZoomRatio = 3.0; } + result = srcBitmap.Load(IP(m_BackgroundName)); + if (resize) { m_ZoomRatio = ratio; } +#else + if (resize) { m_ZoomRatio = 1.0; } + result = srcBitmap.Load(IP(m_BackgroundName)); + if (resize) { m_ZoomRatio = ratio; } +#endif + + HDC hScreenDC = ::GetDC(NULL); // get desktop DC + if(!m_hPal && GetDeviceCaps(hScreenDC, RASTERCAPS) & RC_PALETTE) + { + m_hPal = CreateHalftonePalette(hScreenDC); + } + DeleteDC(hScreenDC); // delete it after use + + if (result) + { + m_bBkImage = TRUE; + CBitmap baseBitmap; + CDC baseDC; + CDC* pWndDC = GetDC(); + +#if _MSC_VER > 1310 + int w = (int)(m_ZoomRatio / 3.0 * srcBitmap.GetWidth()); + int h = (int)(m_ZoomRatio / 3.0 * srcBitmap.GetHeight()); +#else + int w = (int)(m_ZoomRatio * srcBitmap.GetWidth()); + int h = (int)(m_ZoomRatio * srcBitmap.GetHeight()); +#endif + int orgw = srcBitmap.GetWidth(); + int orgh = srcBitmap.GetHeight(); + if(m_hPal && pWndDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE) + { + SelectPalette( pWndDC->GetSafeHdc(), m_hPal, TRUE ); + pWndDC->RealizePalette(); + pWndDC->SetStretchBltMode(HALFTONE); + } + baseBitmap.CreateCompatibleBitmap(pWndDC, orgw, orgh); + baseDC.CreateCompatibleDC(pWndDC); + if(m_hPal && baseDC.GetDeviceCaps(RASTERCAPS) & RC_PALETTE) + { + SelectPalette( baseDC.GetSafeHdc(), m_hPal, FALSE ); + baseDC.RealizePalette(); + baseDC.SetStretchBltMode(HALFTONE); + } + + m_BkBitmap.DeleteObject(); + m_BkDC.DeleteDC(); + m_BkBitmap.CreateCompatibleBitmap(pWndDC, w, h); + m_BkDC.CreateCompatibleDC(pWndDC); + if(m_hPal && baseDC.GetDeviceCaps(RASTERCAPS) & RC_PALETTE) + { + SelectPalette( m_BkDC.GetSafeHdc(), m_hPal, FALSE ); + m_BkDC.RealizePalette(); + m_BkDC.SetStretchBltMode(HALFTONE); + } + + ReleaseDC(pWndDC); + + baseDC.SelectObject(&baseBitmap); + m_BkDC.SelectObject(&m_BkBitmap); + + srcBitmap.BitBlt(baseDC.GetSafeHdc(), 0, 0, SRCCOPY); + srcBitmap.Destroy(); + + m_BkDC.SetStretchBltMode(HALFTONE); + SetBrushOrgEx(m_BkDC, 0, 0, NULL); + m_BkDC.StretchBlt(0, 0, w, h, &baseDC, 0, 0, orgw, orgh, SRCCOPY); + + baseBitmap.DeleteObject(); + baseDC.DeleteDC(); + + m_BrushDlg.DeleteObject(); + m_BrushDlg.CreatePatternBrush(&m_BkBitmap); + + return; + } + else if (m_bHighContrast) + { + m_BrushDlg.DeleteObject(); + m_BrushDlg.CreateSolidBrush(RGB(0, 0, 0)); + } + else + { + CBitmap baseBitmap; + CDC baseDC; + CDC* pWndDC = GetDC(); + + CRect rect; + GetClientRect(&rect); + int w = rect.Width(); + int h = rect.Height(); + + m_BkBitmap.DeleteObject(); + m_BkBitmap.CreateCompatibleBitmap(pWndDC, w, h); + m_BkDC.DeleteDC(); + m_BkDC.CreateCompatibleDC(pWndDC); + m_BkDC.SelectObject(&m_BkBitmap); + + m_BrushDlg.DeleteObject(); + COLORREF bkColor; + + if (m_Background != 0xFFFFFFFF) + { + bkColor = m_Background; + } + else if (bDarkMode) + { + bkColor = RGB(32, 32, 32); + } + else + { + bkColor = RGB(255, 255, 255); + } + m_BrushDlg.CreateSolidBrush(bkColor); + + m_BkDC.FillRect(&rect, &m_BrushDlg); + + ReleaseDC(pWndDC); + } +} + +void CDialogFx::SetWindowTitle(CString title) +{ + SetWindowText(_T(" ") + title + _T(" ")); +} + +void CDialogFx::OnOK() +{ +} + +void CDialogFx::OnCancel() +{ + if (m_bModelessDlg) + { + if (m_MenuId != 0 && m_ParentWnd != NULL) + { + CMenu* menu = m_ParentWnd->GetMenu(); + menu->EnableMenuItem(m_MenuId, MF_ENABLED); + m_ParentWnd->SetMenu(menu); + m_ParentWnd->DrawMenuBar(); + } + CDialog::DestroyWindow(); + } + else + { + CDialog::OnCancel(); + } +} + +//------------------------------------------------ +// Font +//------------------------------------------------ + +int CDialogFx::GetFontScale() +{ + return m_FontScale; +} + +BYTE CDialogFx::GetFontRender() +{ + return m_FontRender; +} + +double CDialogFx::GetFontRatio() +{ + return m_FontRatio; +} + +CString CDialogFx::GetFontFace() +{ + return m_FontFace; +} + +//------------------------------------------------ +// Zoom +//------------------------------------------------ + +DWORD CDialogFx::ChangeZoomType(DWORD zoomType) +{ + DWORD current = (DWORD)ceil(m_Dpi / 96.0 * 100); + int width = GetSystemMetrics(SM_CXSCREEN); + + if(zoomType == ZoomTypeAuto) + { +#if _MSC_VER > 1310 + if (current >= 300) + { + zoomType = ZoomType300; + } + else if (current >= 250) + { + zoomType = ZoomType250; + } + else +#endif + if(current >= 200) + { + zoomType = ZoomType200; + } + else if(current >= 150) + { + zoomType = ZoomType150; + } + else if(current >= 125) + { + zoomType = ZoomType125; + } +#ifdef CRYSTALMARK_RETRO +#ifdef SUISHO_SHIZUKU_SUPPORT + else if (width < 900) { zoomType = ZoomType050; } + else if (width < 1200){ zoomType = ZoomType075; } +#else +// else if (width < 732) { zoomType = ZoomType050; } + else if (width < 732) { zoomType = ZoomType064; } + else if (width < 976) { zoomType = ZoomType075; } +#endif +#endif + else + { + zoomType = ZoomType100; + } + } + + m_ZoomRatio = zoomType / 100.0; + + return zoomType; +} + +//------------------------------------------------ +// Theme +//------------------------------------------------ + +BOOL CDialogFx::IsHighContrast() +{ + HIGHCONTRAST hc = { sizeof(HIGHCONTRAST) }; + SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(HIGHCONTRAST), &hc, 0); + + return hc.dwFlags & HCF_HIGHCONTRASTON; +} + +BOOL CDialogFx::IsDisableDarkMode() +{ + return m_bDisableDarkMode; +} + +//------------------------------------------------ +// Utility +//------------------------------------------------ + +CString CDialogFx::IP(CString imageName) /// ImagePath +{ + CString imagePath; + imagePath.Format(_T("%s%s\\%s-%03d.png"), (LPCTSTR)m_ThemeDir, (LPCTSTR)m_CurrentTheme, (LPCTSTR)imageName, (DWORD)(m_ZoomRatio * 100)); + if (IsFileExist(imagePath)) + { + return imagePath; + } + imagePath.Format(_T("%s%s\\%s-%03d.png"), (LPCTSTR)m_ThemeDir, (LPCTSTR)m_ParentTheme1, (LPCTSTR)imageName, (DWORD)(m_ZoomRatio * 100)); + if (IsFileExist(imagePath)) + { + return imagePath; + } + imagePath.Format(_T("%s%s\\%s-%03d.png"), (LPCTSTR)m_ThemeDir, (LPCTSTR)m_ParentTheme2, (LPCTSTR)imageName, (DWORD)(m_ZoomRatio * 100)); + if (IsFileExist(imagePath)) + { + return imagePath; + } + imagePath.Format(_T("%s%s\\%s-%03d.png"), (LPCTSTR)m_ThemeDir, (LPCTSTR)m_DefaultTheme, (LPCTSTR)imageName, (DWORD)(m_ZoomRatio * 100)); + if (IsFileExist(imagePath)) + { + return imagePath; + } + + return _T(""); +} + +CString CDialogFx::i18n(CString section, CString key, BOOL inEnglish) +{ + TCHAR str[256]; + CString cstr; + + if(inEnglish) + { + GetPrivateProfileStringFx(section, key, _T(""), str, 256, m_DefaultLangPath); + cstr = str; + } + else + { + GetPrivateProfileStringFx(section, key, _T(""), str, 256, m_CurrentLangPath); + cstr = str; + if(cstr.IsEmpty()) + { + GetPrivateProfileStringFx(section, key, _T(""), str, 256, m_DefaultLangPath); + cstr = str; + } + } + + return cstr; +} + +void CDialogFx::OpenUrl(CString url) +{ + INT_PTR result = 0; + result = (INT_PTR)(ShellExecute(NULL, _T("open"), (LPCTSTR)url, NULL, NULL, SW_SHOWNORMAL)); + if(result <= 32) + { + CString args; + args.Format(_T("url.dll,FileProtocolHandler %s"), (LPCTSTR)url); + ShellExecute(NULL, _T("open"), _T("rundll32.exe"), args, NULL, SW_SHOWNORMAL); + } +} + +void CDialogFx::SetLayeredWindow(HWND hWnd, BYTE alpha) +{ +#if _MSC_VER > 1310 + if (IsWin2k()) { return; } + + ::SetWindowLong(hWnd, GWL_EXSTYLE, ::GetWindowLong(hWnd, GWL_EXSTYLE) ^ WS_EX_LAYERED); + ::SetWindowLong(hWnd, GWL_EXSTYLE, ::GetWindowLong(hWnd, GWL_EXSTYLE) | WS_EX_LAYERED); + if (m_bHighContrast) + { + ::SetLayeredWindowAttributes(hWnd, 0, 255, LWA_ALPHA); + } + else + { + ::SetLayeredWindowAttributes(hWnd, 0, alpha, LWA_ALPHA); + } +#endif +} + +//------------------------------------------------ +// MessageMap +//------------------------------------------------ + +void CDialogFx::OnTimer(UINT_PTR nIDEvent) +{ + switch (nIDEvent) + { + case TimerUpdateDialogSizeDpiChanged: + if (m_bDrag) + { + KillTimer(TimerUpdateDialogSizeDpiChanged); + SetTimer(TimerUpdateDialogSizeDpiChanged, TIMER_UPDATE_DIALOG, NULL); + } + else + { + m_bDpiChanging = FALSE; + KillTimer(TimerUpdateDialogSizeDpiChanged); + UpdateDialogSize(); + } + break; + case TimerUpdateDialogSizeDisplayChange: + KillTimer(TimerUpdateDialogSizeDisplayChange); + UpdateDialogSize(); + break; + case TimerUpdateDialogSizeSysColorChange: + KillTimer(TimerUpdateDialogSizeSysColorChange); + UpdateDialogSize(); + break; + case TimerUpdateDialogSizeSettingChange: + KillTimer(TimerUpdateDialogSizeSettingChange); + UpdateDialogSize(); + break; + } +} + +BOOL CDialogFx::OnEraseBkgnd(CDC* pDC) +{ + if (m_bHighContrast) + { + return CDialog::OnEraseBkgnd(pDC); + } + + if(m_hPal && pDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE) + { + SelectPalette( pDC->GetSafeHdc(), m_hPal, TRUE ); + pDC->RealizePalette(); + pDC->SetStretchBltMode(HALFTONE); + } + + CRect rect; + GetClientRect(&rect); + + return pDC->StretchBlt(0, 0, rect.Width(), rect.Height(), &m_BkDC, 0, 0, rect.Width(), rect.Height(), SRCCOPY); +} + +afx_msg LRESULT CDialogFx::OnDpiChanged(WPARAM wParam, LPARAM lParam) +{ + if (m_bInitializing) { return 0; } + + static ULONGLONG preTime = 0; + ULONGLONG currentTime = GetTickCountFx(); + if (currentTime - preTime < 1000) + { + return 0; + } + else + { + preTime = currentTime; + } + + m_Dpi = (INT)HIWORD(wParam); + +#if _MSC_VER > 1310 + if (IsWindowsBuildOrGreater(16299)) // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 + { + ChangeZoomType(m_ZoomType); + m_bDpiChanging = TRUE; + SetTimer(TimerUpdateDialogSizeDpiChanged, TIMER_UPDATE_DIALOG, NULL); + } + else +#endif + if(m_ZoomType == ZoomTypeAuto) + { + DWORD oldZoomRatio = (DWORD)(m_ZoomRatio * 100); + if (ChangeZoomType(m_ZoomType) != oldZoomRatio) + { + m_bDpiChanging = TRUE; + SetTimer(TimerUpdateDialogSizeDpiChanged, TIMER_UPDATE_DIALOG, NULL); + } + } + + return 0; +} + +afx_msg LRESULT CDialogFx::OnDisplayChange(WPARAM wParam, LPARAM lParam) +{ + if (m_bInitializing) { return 0; } + + CDC* cdc = GetDC(); + if (cdc) + { + int color = cdc->GetDeviceCaps(BITSPIXEL) * cdc->GetDeviceCaps(PLANES); + if (color != wParam) + { + SetTimer(TimerUpdateDialogSizeDisplayChange, TIMER_UPDATE_DIALOG, NULL); + } + ReleaseDC(cdc); + } + + return 0; +} + +afx_msg LRESULT CDialogFx::OnSysColorChange(WPARAM wParam, LPARAM lParam) +{ + if (m_bInitializing) { return 0; } + + m_bHighContrast = IsHighContrast(); + + SetTimer(TimerUpdateDialogSizeSysColorChange, TIMER_UPDATE_DIALOG, NULL); + + return 0; +} + +afx_msg LRESULT CDialogFx::OnSettingChange(WPARAM wParam, LPARAM lParam) +{ + if (m_bInitializing) { return 0; } + + if (!lstrcmp(LPCTSTR(lParam), _T("ImmersiveColorSet"))) + { + SetTimer(TimerUpdateDialogSizeSettingChange, TIMER_UPDATE_DIALOG, NULL); + } + + return 0; +} + +afx_msg LRESULT CDialogFx::OnEnterSizeMove(WPARAM wParam, LPARAM lParam) +{ + m_bDrag = TRUE; + + return TRUE; +} + +afx_msg LRESULT CDialogFx::OnExitSizeMove(WPARAM wParam, LPARAM lParam) +{ + m_bDrag = FALSE; + + return TRUE; +} \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/DialogFx.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/DialogFx.h new file mode 100644 index 0000000..3b369d0 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/DialogFx.h @@ -0,0 +1,156 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once + +#include "ImageFx.h" + +class CDialogFx : public CDialog +{ +public: + CDialogFx(UINT dlgResouce, CWnd* pParent = NULL); + virtual ~CDialogFx(); + + // Dialog + virtual BOOL Create(UINT nIDTemplate, CWnd* dlgWnd, UINT menuId, CWnd* pParentWnd = NULL); + + // Font + int GetFontScale(); + BYTE GetFontRender(); + double GetFontRatio(); + CString GetFontFace(); + + // Theme + BOOL IsDisableDarkMode(); + +protected: + // Dialog + virtual BOOL OnInitDialog(); + virtual BOOL PreTranslateMessage(MSG* pMsg); + virtual void PostNcDestroy(); + virtual void UpdateDialogSize(); + virtual void SetClientSize(int sizeX, int sizeY, double zoomRatio); + virtual void UpdateBackground(BOOL resize, BOOL darkMode); + virtual void SetWindowTitle(CString title); + virtual void OnOK(); + virtual void OnCancel(); + + // Zoom + DWORD ChangeZoomType(DWORD zoomType); + + // Theme + BOOL IsHighContrast(); + + // Utility + virtual CString IP(CString imageName); + CString i18n(CString section, CString key, BOOL inEnglish = FALSE); + void OpenUrl(CString url); + void SetLayeredWindow(HWND hWnd, BYTE alpha); + int GetDpi(); + + // MessageMap + DECLARE_MESSAGE_MAP() + afx_msg void OnSize(UINT nType, int cx, int cy); + afx_msg void OnTimer(UINT_PTR nIDEvent); + afx_msg BOOL OnEraseBkgnd(CDC* pDC); + afx_msg LRESULT OnDpiChanged(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnDisplayChange(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnSysColorChange(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnSettingChange(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnEnterSizeMove(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnExitSizeMove(WPARAM wParam, LPARAM lParam); + +protected: + // Dialog + BOOL m_bInitializing; + BOOL m_bDpiChanging; + BOOL m_bShowWindow; + BOOL m_bModelessDlg; + BOOL m_bHighContrast; + BOOL m_bDarkMode; + BOOL m_bDisableDarkMode; + BOOL m_bBkImage; + UINT m_MenuId; + CWnd* m_ParentWnd; + CWnd* m_DlgWnd; + CString m_Ini; + HACCEL m_hAccelerator; + BOOL m_bDrag; + CString m_FontFace; + int m_FontScale; + double m_FontRatio; + BYTE m_FontRender; + HPALETTE m_hPal; + + int m_SizeX; + int m_MaxSizeX; + int m_MinSizeX; + int m_SizeY; + int m_MaxSizeY; + int m_MinSizeY; + + // Zoom + int m_Dpi; + DWORD m_ZoomType; + double m_ZoomRatio; + + // Color for SubClass + COLORREF m_LabelText; + COLORREF m_MeterText; + COLORREF m_ComboText; + COLORREF m_ComboTextSelected; + COLORREF m_ComboBk; + COLORREF m_ComboBkSelected; + COLORREF m_ButtonText; + COLORREF m_EditText; + COLORREF m_EditBk; + COLORREF m_ListText1; + COLORREF m_ListText2; + COLORREF m_ListTextSelected; + COLORREF m_ListBk1; + COLORREF m_ListBk2; + COLORREF m_ListBkSelected; + COLORREF m_ListLine1; + COLORREF m_ListLine2; + COLORREF m_Glass; + COLORREF m_Frame; + COLORREF m_Background; + + BYTE m_ComboAlpha; + BYTE m_EditAlpha; + BYTE m_GlassAlpha; + + BYTE m_CharacterPosition; + + // Theme for SubClass + int m_OffsetX; + CString m_ThemeDir; + CString m_CurrentTheme; + CString m_DefaultTheme; + CString m_ParentTheme1; + CString m_ParentTheme2; + CString m_RandomThemeLabel; + CString m_RandomThemeName; + + // Language for SubClass + CString m_LangDir; + CString m_CurrentLang; + CString m_CurrentLangPath; + CString m_DefaultLangPath; + CString m_BackgroundName; + + // Voice for SubClass + CString m_VoiceDir; + CString m_CurrentVoice; + INT m_VoiceVolume; + + // Class + CBitmap m_BkBitmap; + CDC m_BkDC; + CImage m_BkImage; + CBrush m_BrushDlg; +}; diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/EditFx.cpp b/CristalDiskMark/source/CrystalDiskMark/Priscilla/EditFx.cpp new file mode 100644 index 0000000..8d1c968 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/EditFx.cpp @@ -0,0 +1,629 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "EditFx.h" + +////------------------------------------------------ +// CEditFx +////------------------------------------------------ + +CEditFx::CEditFx() +{ + // Control + m_X = 0; + m_Y = 0; + m_bHighContrast = FALSE; + m_bDarkMode = FALSE; + m_bDrawFrame = FALSE; + m_FrameColor = RGB(128, 128, 128); + m_RenderMode = OwnerDrawImage; + m_bMultiLine = FALSE; + m_BkColor = RGB(255, 255, 255); + + // Glass + m_GlassColor = RGB(255, 255, 255); + m_GlassAlpha = 128; + + // Image + m_ImageCount = 0; + m_BkDC = NULL; + m_bBkBitmapInit = FALSE; + m_bBkLoad = FALSE; + + // Font + m_TextAlign = SS_LEFT; + m_TextColor = RGB(0, 0, 0); + + // Margin + m_Margin.top = 0; + m_Margin.left = 0; + m_Margin.bottom = 0; + m_Margin.right = 0; +} + +CEditFx::~CEditFx() +{ +} + +IMPLEMENT_DYNAMIC(CEditFx, CEdit) + +BEGIN_MESSAGE_MAP(CEditFx, CEdit) + //{{AFX_MSG_MAP(CEditFx) + ON_WM_CTLCOLOR_REFLECT() + ON_CONTROL_REFLECT(EN_CHANGE, OnEnChange) + ON_WM_KEYDOWN() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +//------------------------------------------------ +// Control +//------------------------------------------------ + +BOOL CEditFx::InitControl(int x, int y, int width, int height, double zoomRatio, CDC* bkDC, + LPCTSTR imagePath, int imageCount, DWORD textAlign, int renderMode, BOOL bHighContrast, BOOL bDarkMode, BOOL bDrawFrame, BOOL bMultiLine) +{ + m_X = (int)(x * zoomRatio); + m_Y = (int)(y * zoomRatio); + m_CtrlSize.cx = (int)(width * zoomRatio); + m_CtrlSize.cy = (int)(height * zoomRatio); + MoveWindow(m_X, m_Y, m_CtrlSize.cx, m_CtrlSize.cy); + m_bMultiLine = bMultiLine; + + m_BkDC = bkDC; + m_ImagePath = imagePath; + m_ImageCount = imageCount; + m_RenderMode = renderMode; + + if (ES_LEFT <= textAlign && textAlign <= ES_RIGHT) + { + m_TextAlign = textAlign; + ModifyStyle(0, m_TextAlign); + } + + if (m_ToolTip.m_hWnd != NULL) + { + if (m_ToolTip.GetToolCount() != 0) + { + m_ToolTip.DelTool(this, 1); + } + CRect rect; + GetClientRect(rect); + m_ToolTip.AddTool(this, m_ToolTipText, rect, 1); + } + + m_bHighContrast = bHighContrast; + m_bDarkMode = bDarkMode; + m_bDrawFrame = bDrawFrame; + + if (m_bHighContrast) + { + return TRUE; + } + else if (renderMode & SystemDraw) + { + return TRUE; + } + else + { + SetBkReload(); + LoadCtrlBk(m_BkDC); + } + + if (renderMode & OwnerDrawImage) + { + if (!LoadBitmap(imagePath)) + { + } + } + else + { + m_ImageCount = 1; + m_CtrlImage.Destroy(); + m_CtrlImage.Create(m_CtrlSize.cx, m_CtrlSize.cy * m_ImageCount, 32); + m_CtrlBitmap.Detach(); + m_CtrlBitmap.Attach((HBITMAP)m_CtrlImage); + DWORD length = m_CtrlSize.cx * m_CtrlSize.cy * m_ImageCount * 4; + BYTE* bitmapBits = new BYTE[length]; + m_CtrlBitmap.GetBitmapBits(length, bitmapBits); + + BYTE r, g, b, a; + if (renderMode & OwnerDrawGlass) + { + r = (BYTE)GetRValue(m_GlassColor); + g = (BYTE)GetGValue(m_GlassColor); + b = (BYTE)GetBValue(m_GlassColor); + a = m_GlassAlpha; + } + else // OwnerDrawTransparent + { + r = 0; + g = 0; + b = 0; + a = 0; + } + + for (int y = 0; y < (int)(m_CtrlSize.cy * m_ImageCount); y++) + { + for (int x = 0; x < m_CtrlSize.cx; x++) + { + DWORD p = (y * m_CtrlSize.cx + x) * 4; +#if _MSC_VER > 1310 +#pragma warning( disable : 6386 ) +#endif + bitmapBits[p + 0] = b; + bitmapBits[p + 1] = g; + bitmapBits[p + 2] = r; + bitmapBits[p + 3] = a; +#if _MSC_VER > 1310 +#pragma warning( default : 6386 ) +#endif + } + } + + m_CtrlBitmap.SetBitmapBits(length, bitmapBits); + delete[] bitmapBits; + } + + SetupControlImage(m_BkBitmap, m_CtrlBitmap); + + m_BkBrush.DeleteObject(); + m_BkBrush.CreatePatternBrush(&m_CtrlBitmap); + + Invalidate(); + + return TRUE; +} + +void CEditFx::SetMargin(int top, int left, int bottom, int right, double zoomRatio) +{ + m_Margin.top = (int)(top * zoomRatio); + m_Margin.left = (int)(left * zoomRatio); + m_Margin.bottom = (int)(bottom * zoomRatio); + m_Margin.right = (int)(right * zoomRatio); + + if (GetWindowLongPtr(m_hWnd, GWL_STYLE) & ES_MULTILINE) + { + CRect rectGet, rectSet; + GetRect(rectGet); + + rectSet.top = m_Margin.top; + rectSet.bottom = rectGet.bottom - m_Margin.bottom - m_Margin.top; + rectSet.left = m_Margin.left; + rectSet.right = rectGet.right - m_Margin.right - m_Margin.left; + + SetRect(rectSet); + } + else + { + SetMargins(m_Margin.left, m_Margin.right); + } +} + +CSize CEditFx::GetSize(void) +{ + return m_CtrlSize; +} + +void CEditFx::SetDrawFrame(BOOL bDrawFrame) +{ +#if _MSC_VER > 1310 + if (bDrawFrame) + { + ModifyStyleEx(0, WS_EX_STATICEDGE, SWP_DRAWFRAME); + } + else + { + ModifyStyleEx(WS_EX_STATICEDGE, 0, SWP_DRAWFRAME); + } +#else + if (bDrawFrame) + { + if (IsNT3()) + { + ModifyStyle(0, WS_BORDER, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + else + { + ModifyStyleEx(0, WS_EX_STATICEDGE, SWP_DRAWFRAME); + } + } + else + { + if (IsNT3()) + { + ModifyStyle(WS_BORDER, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + else + { + ModifyStyleEx(WS_EX_STATICEDGE, 0, SWP_DRAWFRAME); + } + } +#endif + m_bDrawFrame = bDrawFrame; +} + +void CEditFx::SetGlassColor(COLORREF glassColor, BYTE glassAlpha) +{ + m_GlassColor = glassColor; + m_GlassAlpha = glassAlpha; +} + +void CEditFx::SetBkColor(COLORREF bkColor) +{ + m_BkColor = bkColor; +} + +void CEditFx::Adjust() +{ + CRect rectGet, rectSet; + GetRect(rectGet); + + CClientDC dc(this); + HGDIOBJ oldFont = dc.SelectObject(&m_Font); +#ifdef UNICODE + CSize size = dc.GetTextExtent(L"ÁAaPpQqYy8!"); +#else + CSize size = dc.GetTextExtent("AaPpQqYy8!"); +#endif + + dc.SelectObject(oldFont); + + rectSet.top = (m_CtrlSize.cy - size.cy) / 2; + rectSet.bottom = rectSet.top + size.cy; + rectSet.left = rectGet.left; + rectSet.right = rectGet.right; + + SetRect(rectSet); +} + +HBRUSH CEditFx::CtlColor(CDC* pDC, UINT nCtlColor) +{ + if (m_bHighContrast) + { + pDC->SetBkMode(TRANSPARENT); + return NULL; + } + else + { +#ifdef UNICODE + pDC->SetTextColor(m_TextColor); + pDC->SetBkMode(TRANSPARENT); + return m_BkBrush; +#else + pDC->SetTextColor(m_TextColor); + pDC->SetBkColor(m_BkColor); + pDC->SetBkMode(OPAQUE); + + return CreateSolidBrush(m_BkColor); +#endif + } +} + +//------------------------------------------------ +// Image +//------------------------------------------------ + +BOOL CEditFx::LoadBitmap(LPCTSTR fileName) +{ + if (m_bHighContrast) { return FALSE; } + if (fileName == NULL) { return FALSE; } + + m_CtrlImage.Destroy(); + m_CtrlImage.Load(fileName); + if (m_CtrlImage.IsNull()) { return FALSE; } + + return LoadBitmap((HBITMAP)m_CtrlImage); +} + +BOOL CEditFx::LoadBitmap(HBITMAP hBitmap) +{ + if (m_bHighContrast) { return FALSE; } + + m_CtrlBitmap.Detach(); + m_CtrlBitmap.Attach(hBitmap); + + return SetBitmap(m_CtrlBitmap); +} + +void CEditFx::SetBkReload(void) +{ + m_bBkBitmapInit = FALSE; + m_bBkLoad = FALSE; +} + +BOOL CEditFx::SetBitmap(CBitmap& bitmap) +{ + if (m_bHighContrast) { return FALSE; } + + BITMAP bitmapInfo; + bitmap.GetBitmap(&bitmapInfo); + + if (m_CtrlSize.cx != bitmapInfo.bmWidth + || m_CtrlSize.cy != bitmapInfo.bmHeight / m_ImageCount) + { + return FALSE; + } + else + { + return TRUE; + } +} + +void CEditFx::LoadCtrlBk(CDC* drawDC) +{ + if (m_bHighContrast) { SetBkReload(); return; } + + if (m_BkBitmap.m_hObject != NULL) + { + BITMAP bitmapInfo; + m_BkBitmap.GetBitmap(&bitmapInfo); + if (bitmapInfo.bmBitsPixel != drawDC->GetDeviceCaps(BITSPIXEL)) + { + SetBkReload(); + } + } + + if (&m_CtrlBitmap != NULL) + { + if (!m_bBkBitmapInit) + { + m_BkBitmap.DeleteObject(); + m_BkBitmap.CreateCompatibleBitmap(drawDC, m_CtrlSize.cx, m_CtrlSize.cy); + m_bBkBitmapInit = TRUE; + } + + if (!m_bBkLoad) + { + CBitmap* pOldBitmap; + CDC* pMemDC = new CDC; + pMemDC->CreateCompatibleDC(drawDC); + pOldBitmap = pMemDC->SelectObject(&m_BkBitmap); + pMemDC->BitBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, m_BkDC, m_X, m_Y, SRCCOPY); + pMemDC->SelectObject(pOldBitmap); + pMemDC->DeleteDC(); + delete pMemDC; + m_bBkLoad = TRUE; + } + } +} + +void CEditFx::SetupControlImage(CBitmap& bkBitmap, CBitmap& ctrlBitmap) +{ + int color = m_BkDC->GetDeviceCaps(BITSPIXEL) * m_BkDC->GetDeviceCaps(PLANES); + + if (!m_CtrlImage.IsNull()) + { + if (m_CtrlImage.GetBPP() == 32) + { + CBitmap* bk32Bitmap; + CImage bk32Image; + if (color == 32) + { + bk32Bitmap = &bkBitmap; + } + else + { + bk32Image.Create(m_CtrlSize.cx, m_CtrlSize.cy, 32); + ::BitBlt(bk32Image.GetDC(), 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, *m_BkDC, m_X, m_Y, SRCCOPY); + bk32Bitmap = CBitmap::FromHandle((HBITMAP)bk32Image); + bk32Image.ReleaseDC(); + } + + BITMAP CtlBmpInfo, DstBmpInfo; + bk32Bitmap->GetBitmap(&DstBmpInfo); + DWORD DstLineBytes = DstBmpInfo.bmWidthBytes; + DWORD DstMemSize = DstLineBytes * DstBmpInfo.bmHeight; + ctrlBitmap.GetBitmap(&CtlBmpInfo); + DWORD CtlLineBytes = CtlBmpInfo.bmWidthBytes; + DWORD CtlMemSize = CtlLineBytes * CtlBmpInfo.bmHeight; + + if ((DstBmpInfo.bmWidthBytes != CtlBmpInfo.bmWidthBytes) + || (DstBmpInfo.bmHeight != CtlBmpInfo.bmHeight / m_ImageCount)) + { + // Error Check // + } + else + { + BYTE* DstBuffer = new BYTE[DstMemSize]; + bk32Bitmap->GetBitmapBits(DstMemSize, DstBuffer); + BYTE* CtlBuffer = new BYTE[CtlMemSize]; + ctrlBitmap.GetBitmapBits(CtlMemSize, CtlBuffer); + + int baseY = 0; + for (LONG py = 0; py < DstBmpInfo.bmHeight; py++) + { + int dn = py * DstLineBytes; + int cn = (baseY + py) * CtlLineBytes; + for (LONG px = 0; px < DstBmpInfo.bmWidth; px++) + { +#if _MSC_VER > 1310 +#pragma warning( disable : 6385 ) +#pragma warning( disable : 6386 ) +#endif + BYTE a = CtlBuffer[cn + 3]; + BYTE na = 255 - a; + CtlBuffer[dn + 0] = (BYTE)((CtlBuffer[cn + 0] * a + DstBuffer[dn + 0] * na) / 255); + CtlBuffer[dn + 1] = (BYTE)((CtlBuffer[cn + 1] * a + DstBuffer[dn + 1] * na) / 255); + CtlBuffer[dn + 2] = (BYTE)((CtlBuffer[cn + 2] * a + DstBuffer[dn + 2] * na) / 255); + dn += (DstBmpInfo.bmBitsPixel / 8); + cn += (CtlBmpInfo.bmBitsPixel / 8); +#if _MSC_VER > 1310 +#pragma warning( default : 6386 ) +#pragma warning( default : 6385 ) +#endif + } + } + + if (color == 32) + { + ctrlBitmap.SetBitmapBits(CtlMemSize, CtlBuffer); + } + else + { + bk32Bitmap->SetBitmapBits(CtlMemSize, CtlBuffer); + m_CtrlImage.Detach(); + m_CtrlImage.Attach(ctrlBitmap); + ::BitBlt(m_CtrlImage.GetDC(), 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, bk32Image.GetDC(), 0, 0, SRCCOPY); + m_CtrlImage.ReleaseDC(); + bk32Image.ReleaseDC(); + + ctrlBitmap.SetBitmapBits(CtlMemSize, CtlBuffer); + } + + if (m_bDrawFrame) + { + HGDIOBJ oldPen; + POINT point; + CPen pen1; pen1.CreatePen(PS_SOLID, 1, RGB(0xF8, 0xF8, 0xF8)); + CPen pen2; pen2.CreatePen(PS_SOLID, 1, RGB(0x98, 0x98, 0x98)); + + HDC hDC = m_CtrlImage.GetDC(); + + oldPen = SelectObject(hDC, pen1); + MoveToEx(hDC, 0, m_CtrlSize.cy - 1, &point); + LineTo(hDC, m_CtrlSize.cx - 1, m_CtrlSize.cy - 1); + LineTo(hDC, m_CtrlSize.cx - 1, 0); + LineTo(hDC, m_CtrlSize.cx - 1, m_CtrlSize.cy - 1); + SelectObject(hDC, pen2); + MoveToEx(hDC, 0, m_CtrlSize.cy - 2, &point); + LineTo(hDC, 0, 0); + LineTo(hDC, m_CtrlSize.cx - 1, 0); + SelectObject(hDC, oldPen); + + pen1.DeleteObject(); + pen2.DeleteObject(); + m_CtrlImage.ReleaseDC(); + } + + delete[] DstBuffer; + delete[] CtlBuffer; + } + } + } +} + +//------------------------------------------------ +// Font +//------------------------------------------------ + +void CEditFx::SetFontEx(CString face, int size, int sizeToolTip, double zoomRatio, double fontRatio, + COLORREF textColor, LONG fontWeight, BYTE fontRender) +{ + LOGFONT logFont = { 0 }; + logFont.lfCharSet = DEFAULT_CHARSET; + logFont.lfHeight = (LONG)(-1 * size * zoomRatio * fontRatio); + logFont.lfQuality = fontRender; + logFont.lfWeight = fontWeight; + if (face.GetLength() < 32) + { + wsprintf(logFont.lfFaceName, _T("%s"), (LPCTSTR)face); + } + else + { + wsprintf(logFont.lfFaceName, _T("")); + } + + m_Font.DeleteObject(); + m_Font.CreateFontIndirect(&logFont); + SetFont(&m_Font); + + logFont.lfHeight = (LONG)(-1 * sizeToolTip * zoomRatio); + m_FontToolTip.DeleteObject(); + m_FontToolTip.CreateFontIndirect(&logFont); + + m_TextColor = textColor; + + if (m_ToolTip.m_hWnd != NULL) + { + m_ToolTip.SetFont(&m_FontToolTip); + } +} + +//------------------------------------------------ +// Message Map +//------------------------------------------------ + +void CEditFx::OnEnChange() +{ + Invalidate(); +} + +void CEditFx::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) +{ + if (nChar == VK_RETURN && m_bMultiLine == FALSE) + { + return ; + } + else + { + CEdit::OnKeyDown(nChar, nRepCnt, nFlags); + } +} + +//------------------------------------------------ +// ToolTip +//------------------------------------------------ + +void CEditFx::SetToolTipText(LPCTSTR text) +{ + if (text == NULL) { return; } + + InitToolTip(); + m_ToolTipText = text; + if (m_ToolTip.GetToolCount() == 0) + { + CRect rect; + GetClientRect(rect); + m_ToolTip.AddTool(this, m_ToolTipText, rect, 1); + } + else + { + m_ToolTip.UpdateTipText(m_ToolTipText, this, 1); + } + + SetToolTipActivate(TRUE); +} + +void CEditFx::SetToolTipActivate(BOOL bActivate) +{ + if (m_ToolTip.GetToolCount() == 0) { return; } + m_ToolTip.Activate(bActivate); +} + +void CEditFx::SetToolTipWindowText(LPCTSTR text) +{ + SetToolTipText(text); + SetWindowText(text); +} + +CString CEditFx::GetToolTipText() +{ + return m_ToolTipText; +} + +void CEditFx::InitToolTip() +{ + if (m_ToolTip.m_hWnd == NULL) + { + m_ToolTip.Create(this, TTS_ALWAYSTIP | TTS_BALLOON | TTS_NOANIMATE | TTS_NOFADE); + m_ToolTip.Activate(FALSE); + m_ToolTip.SetFont(&m_FontToolTip); + m_ToolTip.SendMessage(TTM_SETMAXTIPWIDTH, 0, 1024); + m_ToolTip.SetDelayTime(TTDT_AUTOPOP, 8000); + m_ToolTip.SetDelayTime(TTDT_INITIAL, 500); + m_ToolTip.SetDelayTime(TTDT_RESHOW, 100); + } +} + +BOOL CEditFx::PreTranslateMessage(MSG* pMsg) +{ + InitToolTip(); + m_ToolTip.RelayEvent(pMsg); + + return CEdit::PreTranslateMessage(pMsg); +} \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/EditFx.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/EditFx.h new file mode 100644 index 0000000..8019299 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/EditFx.h @@ -0,0 +1,98 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once + +#include "ImageFx.h" + +class CEditFx : public CEdit +{ + DECLARE_DYNAMIC(CEditFx); + +public: + // Constructors + CEditFx(); + virtual ~CEditFx(); + + // Control + BOOL InitControl(int x, int y, int width, int height, double zoomRatio, CDC* bkDC, LPCTSTR imagePath, + int imageCount, DWORD textAlign, int renderMode, BOOL bHighContrast, BOOL bDarkMode, BOOL bDrawFrame, BOOL bMultiLine = FALSE); + void SetMargin(int top, int left, int bottom, int right, double zoomRatio); + CSize GetSize(void); + void SetDrawFrame(BOOL bDrawFrame); + void SetGlassColor(COLORREF glassColor, BYTE glassAlpha); + void SetBkColor(COLORREF bkColor); + void Adjust(); + + // Font + void SetFontEx(CString face, int size, int sizeToolTip, double zoomRatio, double fontRatio = 1.0, + COLORREF textColor = RGB(0, 0, 0), LONG fontWeight = FW_NORMAL, BYTE fontRender = CLEARTYPE_NATURAL_QUALITY); + + // ToolTip + void SetToolTipText(LPCTSTR text); + void SetToolTipActivate(BOOL bActivate = TRUE); + void SetToolTipWindowText(LPCTSTR text); + CString GetToolTipText(); + +protected: + // Image + BOOL LoadBitmap(LPCTSTR fileName); + BOOL LoadBitmap(HBITMAP hBitmap); + void SetBkReload(void); + BOOL SetBitmap(CBitmap& bitmap); + void LoadCtrlBk(CDC* drawDC); + void SetupControlImage(CBitmap& bkBitmap, CBitmap& ctrlBitmap); + + // ToolTip + void InitToolTip(); + virtual BOOL PreTranslateMessage(MSG* pMsg); + + // MessageMap + DECLARE_MESSAGE_MAP() + afx_msg HBRUSH CtlColor(CDC* pDC, UINT nCtlColor); + afx_msg void OnEnChange(); + afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); + +protected: + // Control + int m_X; + int m_Y; + CSize m_CtrlSize; + CRect m_Margin; + int m_RenderMode; + BOOL m_bHighContrast; + BOOL m_bDarkMode; + BOOL m_bDrawFrame; + COLORREF m_FrameColor; + BOOL m_bMultiLine; + COLORREF m_BkColor; + + // Glass + COLORREF m_GlassColor; + BYTE m_GlassAlpha; + + // Image + CString m_ImagePath; + int m_ImageCount; + CDC* m_BkDC; + CBitmap m_BkBitmap; + CBrush m_BkBrush; + BOOL m_bBkBitmapInit; + BOOL m_bBkLoad; + CBitmap m_CtrlBitmap; + CImage m_CtrlImage; + + // Font + DWORD m_TextAlign; + CFont m_Font; + CFont m_FontToolTip; + COLORREF m_TextColor; + + // ToolTip + CToolTipCtrl m_ToolTip; + CString m_ToolTipText; +}; diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/FontComboBoxFx.cpp b/CristalDiskMark/source/CrystalDiskMark/Priscilla/FontComboBoxFx.cpp new file mode 100644 index 0000000..1583362 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/FontComboBoxFx.cpp @@ -0,0 +1,134 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "FontComboBoxFx.h" + +////------------------------------------------------ +// CFontComboBox +////------------------------------------------------ + +IMPLEMENT_DYNAMIC(CFontComboBox, CComboBoxFx) + +CFontComboBox::CFontComboBox() +{ + m_Brush = NULL; +} + +CFontComboBox::~CFontComboBox() +{ + m_BkBrush.DeleteObject(); +} + +BEGIN_MESSAGE_MAP(CFontComboBox, CComboBoxFx) +END_MESSAGE_MAP() + +void CFontComboBox::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) +{ + if (lpDrawItemStruct->itemID == -1) { return; } + + static COLORREF textColor; + static COLORREF textColorSelected; + static COLORREF bkColor; + static COLORREF bkColorSelected; + + if (m_bHighContrast) + { + textColor = GetTextColor(lpDrawItemStruct->hDC); + textColorSelected = RGB(0, 0, 0); + bkColor = GetBkColor(lpDrawItemStruct->hDC); + bkColorSelected = RGB(0, 255, 255); + + if (bkColor <= RGB(0x80, 0x80, 0x80)) { textColor = RGB(255, 255, 255); } + else { textColor = RGB(0, 0, 0); } + } + else if (m_bDarkMode) + { + textColor = RGB(255, 255, 255); + textColorSelected = RGB(255, 255, 255); + bkColor = RGB(32, 32, 32); + bkColorSelected = RGB(77, 77, 77); + } + else + { + textColor = m_TextColor; + textColorSelected = m_TextColorSelected; + bkColor = m_BkColor; + bkColorSelected = m_BkColorSelected; + } + + CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC); + LoadCtrlBk(pDC); + CString title; + GetLBText(lpDrawItemStruct->itemID, title); + + CFont font; + LOGFONT logfont; + memset(&logfont, 0, sizeof(logfont)); + logfont.lfHeight = m_FontHeight; + logfont.lfWidth = 0; + logfont.lfWeight = FW_NORMAL; + logfont.lfQuality = m_FontRender; + logfont.lfCharSet = DEFAULT_CHARSET; + +#if _MSC_VER <= 1310 + _tcscpy(logfont.lfFaceName, (LPCTSTR)title); +#else + _tcscpy_s(logfont.lfFaceName, 32, (LPCTSTR)title); +#endif + font.CreateFontIndirect(&logfont); + HGDIOBJ oldFont = pDC->SelectObject(&font); + + CBrush Brush; + CBrush* pOldBrush; + if (lpDrawItemStruct->rcItem.left != 0 && !m_bHighContrast) + { + DrawControl(title, pDC, lpDrawItemStruct, m_CtrlBitmap, m_BkBitmap, ControlImageNormal); + Brush.CreateSolidBrush(bkColorSelected); + pOldBrush = pDC->SelectObject(&Brush); + if (lpDrawItemStruct->itemState & ODS_SELECTED) + { + RECT rc = lpDrawItemStruct->rcItem; + // rc.top = (LONG)(rc.bottom - 2 * m_ZoomRatio); + rc.right = (LONG)(rc.left + 3 * m_ZoomRatio); + FillRect(lpDrawItemStruct->hDC, &rc, (HBRUSH)Brush); + } + DrawString(title, pDC, lpDrawItemStruct, textColor); + +#if _MSC_VER <= 1310 + if (IsNT3() && IsWindowEnabled()) + { + DWORD oldTextAlign = m_TextAlign; + HGDIOBJ myoldFont = pDC->SelectStockObject(SYSTEM_FONT); + m_TextAlign = ES_RIGHT; + DrawString(_T("v"), pDC, lpDrawItemStruct, textColor); + m_TextAlign = oldTextAlign; + pDC->SelectObject(myoldFont); + } +#endif + } + else + { + if (lpDrawItemStruct->itemState & ODS_SELECTED) + { + Brush.CreateSolidBrush(bkColorSelected); + pOldBrush = pDC->SelectObject(&Brush); + FillRect(lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem, (HBRUSH)Brush); + DrawString(title, pDC, lpDrawItemStruct, textColorSelected); + } + else + { + Brush.CreateSolidBrush(bkColor); + pOldBrush = pDC->SelectObject(&Brush); + FillRect(lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem, (HBRUSH)Brush); + DrawString(title, pDC, lpDrawItemStruct, textColor); + } + } + pDC->SelectObject(pOldBrush); + Brush.DeleteObject(); + pDC->SelectObject(oldFont); +} diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/FontComboBoxFx.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/FontComboBoxFx.h new file mode 100644 index 0000000..56f1125 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/FontComboBoxFx.h @@ -0,0 +1,27 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once + +#include "ComboBoxFx.h" + +class CFontComboBox : public CComboBoxFx +{ + DECLARE_DYNAMIC(CFontComboBox) + +public: + CFontComboBox(); + virtual ~CFontComboBox(); + +protected: + DECLARE_MESSAGE_MAP() + virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); + + HBRUSH m_Brush; +}; + + diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/HeaderCtrlFx.cpp b/CristalDiskMark/source/CrystalDiskMark/Priscilla/HeaderCtrlFx.cpp new file mode 100644 index 0000000..444c425 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/HeaderCtrlFx.cpp @@ -0,0 +1,203 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "HeaderCtrlFx.h" +#include "OsInfoFx.h" + +IMPLEMENT_DYNAMIC(CHeaderCtrlFx, CHeaderCtrl) + +CHeaderCtrlFx::CHeaderCtrlFx() +{ + m_X = 0; + m_Y = 0; + + m_TextColor = RGB(0, 0, 0); + m_LineColor = RGB(224, 224, 224); + m_BkColor = RGB(255, 255, 255); + m_ZoomRatio = 1.0; + m_FontRatio = 1.0; + m_FontSize = 12; + m_BkDC = NULL; + m_CtrlBitmap = NULL; + m_bHighContrast = FALSE; + m_bDarkMode = FALSE; + m_RenderMode = SystemDraw; +} + +CHeaderCtrlFx::~CHeaderCtrlFx() +{ +} + +BEGIN_MESSAGE_MAP(CHeaderCtrlFx, CHeaderCtrl) + ON_WM_PAINT() + ON_MESSAGE(HDM_LAYOUT, OnLayout) +END_MESSAGE_MAP() + +void CHeaderCtrlFx::InitControl(int x, int y, double zoomRatio, CDC* bkDC, CBitmap* ctrlBitmap, COLORREF textColor, COLORREF bkColor, COLORREF lineColor, int renderMode, BOOL bHighContrast, BOOL bDarkMode) +{ + m_X = (int)(x * zoomRatio); + m_Y = (int)(y * zoomRatio); + m_ZoomRatio = zoomRatio; + m_BkDC = bkDC; + m_TextColor = textColor; + m_LineColor = lineColor; + m_BkColor = bkColor; + + m_CtrlBitmap = ctrlBitmap; + m_RenderMode = renderMode; + m_bHighContrast = bHighContrast; + m_bDarkMode = bDarkMode; +} + +void CHeaderCtrlFx::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) +{ + if (m_bHighContrast || m_RenderMode & SystemDraw) + { + return CHeaderCtrl::DrawItem(lpDrawItemStruct); + } + + CDC* drawDC = CDC::FromHandle(lpDrawItemStruct->hDC); + + drawDC->SetBkMode(TRANSPARENT); + drawDC->SetTextColor(m_TextColor); + + CRect clientRect; + GetClientRect(&clientRect); + + CDC BkDC; + BkDC.CreateCompatibleDC(m_BkDC); + BkDC.SelectObject(m_CtrlBitmap); + CRect rc = lpDrawItemStruct->rcItem; + CBrush br; + + if (m_CtrlBitmap != NULL) + { + drawDC->BitBlt(rc.left, rc.top, rc.right, rc.bottom, &BkDC, rc.left, rc.top, SRCCOPY); + } + else + { + br.CreateSolidBrush(m_BkColor); + drawDC->FillRect(&rc, &br); + } + + br.DeleteObject(); + br.CreateSolidBrush(m_LineColor); + + CRect rect = lpDrawItemStruct->rcItem; + rect.left = rect.right - 1; + drawDC->FillRect(&rect, &br); + + rect = lpDrawItemStruct->rcItem; + rect.top = rect.bottom - 1; + drawDC->FillRect(&rect, &br); + + HDITEM hi{}; + TCHAR str[256]{}; + hi.mask = HDI_TEXT | HDI_FORMAT; + hi.pszText = str; + hi.cchTextMax = 256; + GetItem(lpDrawItemStruct->itemID, &hi); + + rect = (CRect)(lpDrawItemStruct->rcItem); + + if (hi.fmt & HDF_CENTER) + { + drawDC->DrawText(hi.pszText, lstrlen(hi.pszText), rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + } + else if (hi.fmt & HDF_RIGHT) + { + rect.right -= 6; + drawDC->DrawText(hi.pszText, lstrlen(hi.pszText), rect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); + } + else + { + rect.left += 6; + drawDC->DrawText(hi.pszText, lstrlen(hi.pszText), rect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + } +} + +void CHeaderCtrlFx::OnPaint() +{ + if (m_bHighContrast || m_RenderMode & SystemDraw) + { + return CHeaderCtrl::OnPaint(); + } + + CHeaderCtrl::OnPaint(); + + RECT rectRightItem; + int iItemCount = Header_GetItemCount(this->m_hWnd); + if (iItemCount > 0) + { + Header_GetItemRect(this->m_hWnd, (WPARAM)iItemCount - 1, &rectRightItem); + RECT rectClient; + GetClientRect(&rectClient); + if (rectRightItem.right < rectClient.right) + { + CDC* drawDC = GetDC(); + if (m_CtrlBitmap != NULL) + { + CDC BkDC; + BkDC.CreateCompatibleDC(m_BkDC); + BkDC.SelectObject(m_CtrlBitmap); + drawDC->BitBlt(rectRightItem.right, rectClient.top, rectClient.right, rectClient.bottom, &BkDC, rectRightItem.right, rectRightItem.top, SRCCOPY); + } + else + { + CBrush br; + br.CreateSolidBrush(m_BkColor); + rectClient.left = rectRightItem.right; + drawDC->FillRect(&rectClient, &br); + } + } + } +} + +LRESULT CHeaderCtrlFx::OnLayout(WPARAM wParam, LPARAM lParam) +{ + LRESULT lResult = CHeaderCtrl::DefWindowProc(HDM_LAYOUT, 0, lParam); + + if (IsWinXpLuna()) + { + HD_LAYOUT& hdl = *(HD_LAYOUT*)lParam; + RECT* prc = hdl.prc; + WINDOWPOS* pwpos = hdl.pwpos; + + int nHeight = (int)(pwpos->cy * (m_ZoomRatio * m_FontRatio)); + + pwpos->cy = nHeight; + prc->top = nHeight; + } + + return lResult; +} + +void CHeaderCtrlFx::SetFontEx(CString face, int size, double zoomRatio, double fontRatio, LONG fontWeight, BYTE fontRender) +{ + m_FontSize = size; + m_ZoomRatio = zoomRatio; + m_FontRatio = fontRatio; + + LOGFONT logFont = { 0 }; + logFont.lfCharSet = DEFAULT_CHARSET; + logFont.lfHeight = (LONG)(-1 * size * zoomRatio * fontRatio); + logFont.lfQuality = fontRender; + logFont.lfWeight = fontWeight; + if (face.GetLength() < 32) + { + wsprintf(logFont.lfFaceName, _T("%s"), (LPCTSTR)face); + } + else + { + wsprintf(logFont.lfFaceName, _T("")); + } + + m_Font.DeleteObject(); + m_Font.CreateFontIndirect(&logFont); + SetFont(&m_Font); +} \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/HeaderCtrlFx.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/HeaderCtrlFx.h new file mode 100644 index 0000000..e36bfed --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/HeaderCtrlFx.h @@ -0,0 +1,45 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once + +class CHeaderCtrlFx : public CHeaderCtrl +{ + DECLARE_DYNAMIC(CHeaderCtrlFx) + +public: + CHeaderCtrlFx(); + virtual ~CHeaderCtrlFx(); + void InitControl(int x, int y, double zoomRatio, CDC* bkDC, CBitmap* ctrlBitmap, COLORREF textColor, COLORREF bkColor, COLORREF lineColor, int renderMode, BOOL bHighContrast, BOOL bDarkMode); + void SetFontEx(CString face, int size, double zoomRatio, double fontRatio, LONG fontWeight, BYTE fontRender); + +protected: + // Draw Control + virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); + + DECLARE_MESSAGE_MAP() + afx_msg void OnPaint(); + afx_msg LRESULT OnLayout(WPARAM wParam, LPARAM lParam); + + int m_X; + int m_Y; + COLORREF m_TextColor; + COLORREF m_BkColor; + COLORREF m_LineColor; + double m_ZoomRatio; + double m_FontRatio; + int m_RenderMode; + BOOL m_bHighContrast; + BOOL m_bDarkMode; + + CFont m_Font; + int m_FontSize; + CDC* m_BkDC; + CBitmap* m_CtrlBitmap; +}; + + diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/IatHook.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/IatHook.h new file mode 100644 index 0000000..b1406a3 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/IatHook.h @@ -0,0 +1,99 @@ +/*---------------------------------------------------------------------------*/ +// Author : Richard Yu +// Web : https://github.com/ysc3839/win32-darkmode +// License : The MIT License +// https://github.com/ysc3839/win32-darkmode/blob/master/LICENSE +/*---------------------------------------------------------------------------*/ +// This file contains code from +// https://github.com/stevemk14ebr/PolyHook_2_0/blob/master/sources/IatHook.cpp +// which is licensed under the MIT License. +// See PolyHook_2_0-LICENSE for more information. + +#pragma once + +#include + +template +constexpr T RVA2VA(T1 base, T2 rva) +{ + return reinterpret_cast(reinterpret_cast(base) + rva); +} + +template +constexpr T DataDirectoryFromModuleBase(void *moduleBase, size_t entryID) +{ + auto dosHdr = reinterpret_cast(moduleBase); + auto ntHdr = RVA2VA(moduleBase, dosHdr->e_lfanew); + auto dataDir = ntHdr->OptionalHeader.DataDirectory; + return RVA2VA(moduleBase, dataDir[entryID].VirtualAddress); +} + +PIMAGE_THUNK_DATA FindAddressByName(void *moduleBase, PIMAGE_THUNK_DATA impName, PIMAGE_THUNK_DATA impAddr, const char *funcName) +{ + for (; impName->u1.Ordinal; ++impName, ++impAddr) + { + if (IMAGE_SNAP_BY_ORDINAL(impName->u1.Ordinal)) + continue; + + auto import = RVA2VA(moduleBase, impName->u1.AddressOfData); + if (strcmp(import->Name, funcName) != 0) + continue; + return impAddr; + } + return nullptr; +} + +PIMAGE_THUNK_DATA FindAddressByOrdinal(void *moduleBase, PIMAGE_THUNK_DATA impName, PIMAGE_THUNK_DATA impAddr, uint16_t ordinal) +{ + for (; impName->u1.Ordinal; ++impName, ++impAddr) + { + if (IMAGE_SNAP_BY_ORDINAL(impName->u1.Ordinal) && IMAGE_ORDINAL(impName->u1.Ordinal) == ordinal) + return impAddr; + } + return nullptr; +} + +PIMAGE_THUNK_DATA FindIatThunkInModule(void *moduleBase, const char *dllName, const char *funcName) +{ + auto imports = DataDirectoryFromModuleBase(moduleBase, IMAGE_DIRECTORY_ENTRY_IMPORT); + for (; imports->Name; ++imports) + { + if (_stricmp(RVA2VA(moduleBase, imports->Name), dllName) != 0) + continue; + + auto origThunk = RVA2VA(moduleBase, imports->OriginalFirstThunk); + auto thunk = RVA2VA(moduleBase, imports->FirstThunk); + return FindAddressByName(moduleBase, origThunk, thunk, funcName); + } + return nullptr; +} + +PIMAGE_THUNK_DATA FindDelayLoadThunkInModule(void *moduleBase, const char *dllName, const char *funcName) +{ + auto imports = DataDirectoryFromModuleBase(moduleBase, IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT); + for (; imports->DllNameRVA; ++imports) + { + if (_stricmp(RVA2VA(moduleBase, imports->DllNameRVA), dllName) != 0) + continue; + + auto impName = RVA2VA(moduleBase, imports->ImportNameTableRVA); + auto impAddr = RVA2VA(moduleBase, imports->ImportAddressTableRVA); + return FindAddressByName(moduleBase, impName, impAddr, funcName); + } + return nullptr; +} + +PIMAGE_THUNK_DATA FindDelayLoadThunkInModule(void *moduleBase, const char *dllName, uint16_t ordinal) +{ + auto imports = DataDirectoryFromModuleBase(moduleBase, IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT); + for (; imports->DllNameRVA; ++imports) + { + if (_stricmp(RVA2VA(moduleBase, imports->DllNameRVA), dllName) != 0) + continue; + + auto impName = RVA2VA(moduleBase, imports->ImportNameTableRVA); + auto impAddr = RVA2VA(moduleBase, imports->ImportAddressTableRVA); + return FindAddressByOrdinal(moduleBase, impName, impAddr, ordinal); + } + return nullptr; +} diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/ImageFx.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ImageFx.h new file mode 100644 index 0000000..19a3c1f --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ImageFx.h @@ -0,0 +1,420 @@ +#pragma once +#include +#include + +#include "stb_image.h" +#include "stb_image_write.h" + +class CImage +{ +public: + enum + { + createAlphaChannel = 0x01 + }; + + CImage() + { + Init(); + } + + ~CImage() + { + Destroy(); + } + + operator HBITMAP() const { return m_hBitmap; } + BOOL IsNull() const { return m_hBitmap == NULL; } + + int GetWidth() const { return m_width; } + int GetHeight() const { return m_height; } + int GetPitch() const { return m_pitch; } + int GetBPP() const { return 32; } + void* GetBits() const { return m_bits; } + + BOOL Create(int w, int h, int bpp, DWORD flags = 0) + { + Destroy(); + + if (bpp != 32) return FALSE; + + BITMAPINFO bmi = {}; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = w; + bmi.bmiHeader.biHeight = -h; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + + void* bits = NULL; + + m_hBitmap = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, &bits, NULL, 0); + if (!m_hBitmap) return FALSE; + + m_bits = (BYTE*)bits; + m_width = w; + m_height = h; + m_pitch = w * 4; + + m_hasAlpha = (flags & createAlphaChannel) != 0; + + memset(m_bits, 0, w * h * 4); + + return TRUE; + } + + void Destroy() + { + ReleaseDC(); + + if (m_hBitmap) + { + DeleteObject(m_hBitmap); + m_hBitmap = NULL; + } + + Init(); + } + + BOOL Load(LPCTSTR fileName) + { + Destroy(); + + HANDLE hFile = CreateFile(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hFile == INVALID_HANDLE_VALUE) + { + return FALSE; + } + + DWORD size = GetFileSize(hFile, NULL); + BYTE* buf = (BYTE*)malloc(size); + + DWORD read = 0; + if (ReadFile(hFile, buf, size, &read, NULL) == FALSE || read != size) + { + free(buf); + CloseHandle(hFile); + return FALSE; + } + + BOOL result = FALSE; + + if (IsPNG(buf)) + { + result = LoadPNG(buf, size); + } + else if (IsBMP(buf)) + { + result = LoadBMP(buf, size); + } + + free(buf); + return result; + } + + static void png_write_func(void* context, void* data, int size) + { + HANDLE hFile = (HANDLE)context; + DWORD written = 0; + WriteFile(hFile, data, size, &written, NULL); + } + + BOOL Save(LPCTSTR fileName) + { + if (!m_bits || !fileName) + return FALSE; + + if (IsPNGFileName(fileName)) + return SavePNG(fileName); + + if (IsBMPFileName(fileName)) + return SaveBMP(fileName); + + return FALSE; + } + + BOOL IsPNGFileName(LPCTSTR fileName) + { + LPCTSTR ext = _tcsrchr(fileName, _T('.')); + if (!ext) return FALSE; + return _tcsicmp(ext, _T(".png")) == 0; + } + + BOOL IsBMPFileName(LPCTSTR fileName) + { + LPCTSTR ext = _tcsrchr(fileName, _T('.')); + if (!ext) return FALSE; + return _tcsicmp(ext, _T(".bmp")) == 0; + } + + BOOL SavePNG(LPCTSTR fileName) + { + HANDLE hFile = CreateFile( + fileName, + GENERIC_WRITE, + 0, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (hFile == INVALID_HANDLE_VALUE) + return FALSE; + + size_t size = (size_t)m_width * m_height * 4; + BYTE* tmp = (BYTE*)malloc(size); + if (!tmp) { + CloseHandle(hFile); + return FALSE; + } + + for (int y = 0; y < m_height; y++) + { + BYTE* src = m_bits + y * m_pitch; + BYTE* dst = tmp + y * m_width * 4; + + for (int x = 0; x < m_width; x++) + { + dst[x * 4 + 0] = src[x * 4 + 2]; + dst[x * 4 + 1] = src[x * 4 + 1]; + dst[x * 4 + 2] = src[x * 4 + 0]; + dst[x * 4 + 3] = 255; + } + } + + int result = stbi_write_png_to_func( + png_write_func, + (void*)hFile, + m_width, + m_height, + 4, + tmp, + m_width * 4); + + free(tmp); + CloseHandle(hFile); + + return result != 0; + } + + BOOL SaveBMP(LPCTSTR fileName) + { + HANDLE hFile = CreateFile( + fileName, + GENERIC_WRITE, + 0, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (hFile == INVALID_HANDLE_VALUE) + return FALSE; + + int rowSize = ((m_width * 3 + 3) / 4) * 4; + int dataSize = rowSize * m_height; + + BITMAPFILEHEADER bf = {}; + bf.bfType = 0x4D42; // 'BM' + bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); + bf.bfSize = bf.bfOffBits + dataSize; + + BITMAPINFOHEADER bi = {}; + bi.biSize = sizeof(BITMAPINFOHEADER); + bi.biWidth = m_width; + bi.biHeight = m_height; // bottom-up + bi.biPlanes = 1; + bi.biBitCount = 24; + bi.biCompression = BI_RGB; + + DWORD written = 0; + + WriteFile(hFile, &bf, sizeof(bf), &written, NULL); + WriteFile(hFile, &bi, sizeof(bi), &written, NULL); + + BYTE* line = (BYTE*)malloc(rowSize); + if (!line) + { + CloseHandle(hFile); + return FALSE; + } + + for (int y = m_height - 1; y >= 0; y--) + { + BYTE* src = m_bits + y * m_pitch; + BYTE* dst = line; + + memset(line, 0, rowSize); + + for (int x = 0; x < m_width; x++) + { + dst[0] = src[0]; // B + dst[1] = src[1]; // G + dst[2] = src[2]; // R + + src += 4; + dst += 3; + } + + if (!WriteFile(hFile, line, rowSize, &written, NULL) || written != (DWORD)rowSize) + { + free(line); + CloseHandle(hFile); + return FALSE; + } + } + + free(line); + CloseHandle(hFile); + return TRUE; + } + + BOOL BitBlt(HDC hDest, int x, int y, DWORD rop = SRCCOPY) const + { + return BitBlt(hDest, x, y, m_width, m_height, 0, 0, rop); + } + + BOOL BitBlt(HDC hDest, int x, int y, int w, int h, int sx, int sy, DWORD rop) const + { + HDC hSrc = GetDC(); + + BOOL r = ::BitBlt(hDest, x, y, w, h, hSrc, sx, sy, rop); + + ReleaseDC(); + return r; + } + + COLORREF GetPixel(int x, int y) const + { + BYTE* p = m_bits + y * m_pitch + x * 4; + return RGB(p[2], p[1], p[0]); + } + + HDC GetDC() const + { + if (!m_hDC) + { + m_hDC = CreateCompatibleDC(NULL); + m_hOldBitmap = (HBITMAP)SelectObject(m_hDC, m_hBitmap); + } + return m_hDC; + } + + void ReleaseDC() const + { + if (m_hDC) + { + SelectObject(m_hDC, m_hOldBitmap); + DeleteDC(m_hDC); + m_hDC = NULL; + m_hOldBitmap = NULL; + } + } + + void Attach(HBITMAP hBitmap) + { + Destroy(); + + m_hBitmap = hBitmap; + + BITMAP bm; + GetObject(hBitmap, sizeof(bm), &bm); + + m_width = bm.bmWidth; + m_height = bm.bmHeight; + m_pitch = bm.bmWidthBytes; + m_bits = (BYTE*)bm.bmBits; + + m_hasAlpha = (bm.bmBitsPixel == 32); + } + + HBITMAP Detach() + { + HBITMAP h = m_hBitmap; + Init(); + return h; + } + +private: + void Init() + { + m_hBitmap = NULL; + m_bits = NULL; + m_width = m_height = m_pitch = 0; + m_hDC = NULL; + m_hOldBitmap = NULL; + m_hasAlpha = false; + } + + BOOL IsPNG(BYTE* p) + { + static BYTE sig[8] = { 137,80,78,71,13,10,26,10 }; + return memcmp(p, sig, 8) == 0; + } + + BOOL IsBMP(BYTE* p) + { + return p[0] == 'B' && p[1] == 'M'; + } + + BOOL LoadPNG(BYTE* data, int size) + { + int w, h, c; + unsigned char* img = stbi_load_from_memory(data, size, &w, &h, &c, 4); + if (!img) return FALSE; + + Create(w, h, 32, createAlphaChannel); + + for (int i = 0; i < w * h; i++) + { + BYTE r = img[i * 4 + 0]; + BYTE g = img[i * 4 + 1]; + BYTE b = img[i * 4 + 2]; + BYTE a = img[i * 4 + 3]; + + m_bits[i * 4 + 0] = b; + m_bits[i * 4 + 1] = g; + m_bits[i * 4 + 2] = r; + m_bits[i * 4 + 3] = a; + } + + stbi_image_free(img); + return TRUE; + } + + BOOL LoadBMP(BYTE* data, int) + { + BITMAPFILEHEADER* bf = (BITMAPFILEHEADER*)data; + BITMAPINFOHEADER* bi = (BITMAPINFOHEADER*)(data + sizeof(BITMAPFILEHEADER)); + + BYTE* src = data + bf->bfOffBits; + + Create(bi->biWidth, abs(bi->biHeight), 32); + + for (int y = 0; y < m_height; y++) + { + BYTE* d = m_bits + y * m_pitch; + BYTE* s = src + (m_height - 1 - y) * ((m_width * 3 + 3) & ~3); + + for (int x = 0; x < m_width; x++) + { + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = 255; + d += 4; s += 3; + } + } + return TRUE; + } + +private: + HBITMAP m_hBitmap; + BYTE* m_bits; + int m_width, m_height, m_pitch; + + mutable HDC m_hDC; + mutable HBITMAP m_hOldBitmap; + + bool m_hasAlpha; +}; \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/ImageToast.cpp b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ImageToast.cpp new file mode 100644 index 0000000..1670ebf --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ImageToast.cpp @@ -0,0 +1,269 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "ImageToast.h" + +#ifndef CMK_MIN +#define CMK_MIN(a,b) (( (a) < (b) ) ? (a) : (b)) +#endif +#ifndef CMK_MAX +#define CMK_MAX(a,b) (( (a) > (b) ) ? (a) : (b)) +#endif + +using namespace Gdiplus; + +static const UINT IDT_CLOSE = 1; +static const UINT IDT_FADE = 2; + +BEGIN_MESSAGE_MAP(CImageToast, CWnd) + ON_WM_CREATE() + ON_WM_TIMER() + ON_WM_LBUTTONDOWN() + ON_WM_RBUTTONDOWN() + ON_WM_ERASEBKGND() + ON_WM_KEYDOWN() +END_MESSAGE_MAP() + +CImageToast::CImageToast() {} +CImageToast::~CImageToast() { + if (m_hDib) { ::DeleteObject(m_hDib); m_hDib = nullptr; } +} + +int CImageToast::OnCreate(LPCREATESTRUCT lpCreateStruct) +{ + if (CWnd::OnCreate(lpCreateStruct) == -1) + return -1; + return 0; +} + +BOOL CImageToast::OnEraseBkgnd(CDC* /*pDC*/) { return TRUE; } + +void CImageToast::OnLButtonDown(UINT, CPoint) +{ + OpenUrlIfAny(); + BeginClose(FALSE); +} + +void CImageToast::OnRButtonDown(UINT, CPoint) +{ + BeginClose(FALSE); +} + +void CImageToast::OnKeyDown(UINT, UINT, UINT) +{ + BeginClose(FALSE); +} + +BOOL CImageToast::EnsureWindowCreated() +{ + if (m_hWnd && ::IsWindow(m_hWnd)) return TRUE; + + CString cls = AfxRegisterWndClass(0, ::LoadCursor(nullptr, IDC_HAND), 0, 0); + DWORD ex = WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_LAYERED; + DWORD st = WS_POPUP; + + if (!CreateEx(ex, cls, _T(""), st, CRect(0,0,0,0), nullptr, 0)) + return FALSE; + + ShowWindow(SW_SHOWNOACTIVATE); + return TRUE; +} + +static BOOL CreateARGBDIB(HDC hdc, int w, int h, HBITMAP& outBmp, void** outBits) +{ + BITMAPINFO bi{}; + bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bi.bmiHeader.biWidth = w; + bi.bmiHeader.biHeight = -h; // top-down + bi.bmiHeader.biPlanes = 1; + bi.bmiHeader.biBitCount = 32; + bi.bmiHeader.biCompression = BI_RGB; + + void* bits = nullptr; + HBITMAP hbmp = CreateDIBSection(hdc, &bi, DIB_RGB_COLORS, &bits, nullptr, 0); + if (!hbmp || !bits) return FALSE; + outBmp = hbmp; *outBits = bits; return TRUE; +} + +BOOL CImageToast::LoadPngToDIB(LPCWSTR path) +{ + Gdiplus::Bitmap bmp(path); + if (bmp.GetLastStatus() != Gdiplus::Ok) return FALSE; + + const int w = (int)bmp.GetWidth(); + const int h = (int)bmp.GetHeight(); + if (w <= 0 || h <= 0) return FALSE; + + HDC hdcScreen = ::GetDC(nullptr); + + HBITMAP hbmp = nullptr; + void* bits = nullptr; + if (!CreateARGBDIB(hdcScreen, w, h, hbmp, &bits)) { + ::ReleaseDC(nullptr, hdcScreen); + return FALSE; + } + + HDC hdcMem = ::CreateCompatibleDC(hdcScreen); + HGDIOBJ oldBmp = ::SelectObject(hdcMem, hbmp); + + { + Gdiplus::Graphics g(hdcMem); + g.SetCompositingMode(Gdiplus::CompositingModeSourceCopy); + Gdiplus::SolidBrush clearBrush(Gdiplus::Color(0, 0, 0, 0)); + g.FillRectangle(&clearBrush, 0, 0, w, h); + g.DrawImage(&bmp, 0, 0, w, h); + } + + ::SelectObject(hdcMem, oldBmp); + ::DeleteDC(hdcMem); + ::ReleaseDC(nullptr, hdcScreen); + + if (m_hDib) ::DeleteObject(m_hDib); + m_hDib = hbmp; + m_bmpSize.cx = w; m_bmpSize.cy = h; + return TRUE; +} + +void CImageToast::UpdateLayered() +{ + if (!m_hWnd || !::IsWindow(m_hWnd) || !m_hDib) return; + + HDC hdcScreen = ::GetDC(nullptr); + HDC hdcMem = ::CreateCompatibleDC(hdcScreen); + HBITMAP hOld = (HBITMAP)::SelectObject(hdcMem, m_hDib); + + POINT ptSrc{ 0,0 }; + SIZE sz{ m_bmpSize.cx, m_bmpSize.cy }; + BLENDFUNCTION bf{}; + bf.BlendOp = AC_SRC_OVER; + bf.SourceConstantAlpha = m_curAlpha; + bf.AlphaFormat = AC_SRC_ALPHA; + + POINT cursor{}; ::GetCursorPos(&cursor); + HMONITOR mon = ::MonitorFromPoint(cursor, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi{ sizeof(mi) }; ::GetMonitorInfo(mon, &mi); + RECT wa = mi.rcWork; + + POINT ptDst{}; + ptDst.x = wa.right - sz.cx - m_margin; + ptDst.y = wa.bottom - sz.cy - m_margin; + + ::UpdateLayeredWindow(m_hWnd, hdcScreen, &ptDst, &sz, hdcMem, &ptSrc, 0, &bf, ULW_ALPHA); + + ::SelectObject(hdcMem, hOld); + ::DeleteDC(hdcMem); + ::ReleaseDC(nullptr, hdcScreen); +} + +void CImageToast::StartFadeTimer(BOOL fadeIn) +{ + if (!m_enableFade) return; + m_closing = !fadeIn; + SetTimer(IDT_FADE, 16, nullptr); +} + +void CImageToast::BeginClose(BOOL force) +{ + if (!m_hWnd) return; + + if (force || !m_enableFade) { + KillTimer(IDT_FADE); + KillTimer(IDT_CLOSE); + DestroyWindow(); + return; + } + if (!m_closing) { + m_closing = TRUE; + SetTimer(IDT_FADE, 16, nullptr); + } +} + +void CImageToast::OnTimer(UINT_PTR id) +{ + if (id == IDT_CLOSE) { + KillTimer(IDT_CLOSE); + BeginClose(FALSE); + return; + } + if (id == IDT_FADE) { + int stepIn = CMK_MAX(1, (int)m_maxAlpha * 16 / CMK_MAX(1, m_fadeInMs)); + int stepOut = CMK_MAX(1, (int)m_maxAlpha * 16 / CMK_MAX(1, m_fadeOutMs)); + + if (!m_closing) { + if (m_curAlpha < m_maxAlpha) { + m_curAlpha = (BYTE)CMK_MIN((int)m_maxAlpha, (int)m_curAlpha + stepIn); + UpdateLayered(); + } + else { + KillTimer(IDT_FADE); + } + } + else { + if (m_curAlpha > 0) { + m_curAlpha = (BYTE)CMK_MAX(0, (int)m_curAlpha - stepOut); + UpdateLayered(); + } + else { + KillTimer(IDT_FADE); + DestroyWindow(); + } + } + return; + } + CWnd::OnTimer(id); +} + +BOOL CImageToast::Show(LPCWSTR pngPath, UINT showMillis, BOOL enableFade, BYTE maxAlpha, + int margin, int fadeInMs, int fadeOutMs, LPCWSTR urlToOpen) +{ + m_pngPath = pngPath; + m_showMillis = showMillis; + m_enableFade = enableFade; + m_maxAlpha = maxAlpha; + m_margin = margin; + m_fadeInMs = fadeInMs; + m_fadeOutMs = fadeOutMs; + m_curAlpha = enableFade ? 0 : maxAlpha; + m_closing = FALSE; + m_opened = FALSE; + m_url = (urlToOpen ? urlToOpen : L""); + + if (!EnsureWindowCreated()) return FALSE; + if (!LoadPngToDIB(m_pngPath)) { + BeginClose(TRUE); + return FALSE; + } + + UpdateLayered(); + ShowWindow(SW_SHOWNOACTIVATE); + + SetTimer(IDT_CLOSE, m_showMillis, nullptr); + if (m_enableFade) StartFadeTimer(TRUE); + + return TRUE; +} + +void CImageToast::SetLink(LPCWSTR urlToOpen) +{ + m_url = (urlToOpen ? urlToOpen : L""); +} + +void CImageToast::CloseNow() +{ + BeginClose(FALSE); +} + +void CImageToast::OpenUrlIfAny() +{ + if (m_opened) return; + if (m_url.IsEmpty()) return; + + m_opened = TRUE; + + ::ShellExecuteW(nullptr, _T("open"), m_url, nullptr, nullptr, SW_SHOWNORMAL); +} diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/ImageToast.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ImageToast.h new file mode 100644 index 0000000..a568300 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ImageToast.h @@ -0,0 +1,60 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once +#include +#pragma comment(lib, "gdiplus.lib") +#pragma comment(lib, "Shcore.lib") + +class CImageToast : public CWnd +{ +public: + CImageToast(); + virtual ~CImageToast(); + + BOOL Show(LPCWSTR pngPath, UINT showMillis = 30000, + BOOL enableFade = TRUE, BYTE maxAlpha = 255, + int margin = 16, int fadeInMs = 200, int fadeOutMs = 250, + LPCWSTR urlToOpen = nullptr); + + void SetLink(LPCWSTR urlToOpen); + + void CloseNow(); + +protected: + afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); + afx_msg void OnTimer(UINT_PTR nIDEvent); + afx_msg void OnLButtonDown(UINT nFlags, CPoint point); + afx_msg void OnRButtonDown(UINT nFlags, CPoint point); + afx_msg BOOL OnEraseBkgnd(CDC* pDC); + afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); + DECLARE_MESSAGE_MAP() + + BOOL EnsureWindowCreated(); + BOOL LoadPngToDIB(LPCWSTR path); + void UpdateLayered(); + void BeginClose(BOOL force = FALSE); + void StartFadeTimer(BOOL fadeIn); + + void OpenUrlIfAny(); + +private: + CString m_pngPath; + UINT m_showMillis{ 30000 }; + BOOL m_enableFade{ TRUE }; + BYTE m_maxAlpha{ 255 }; + int m_margin{ 16 }; + int m_fadeInMs{ 200 }; + int m_fadeOutMs{ 250 }; + CString m_url; + + HBITMAP m_hDib{ nullptr }; + SIZE m_bmpSize{ 0,0 }; + BYTE m_curAlpha{ 0 }; + BOOL m_closing{ FALSE }; + BOOL m_opened{ FALSE }; +}; diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/ListCtrlFx.cpp b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ListCtrlFx.cpp new file mode 100644 index 0000000..e4f81e6 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ListCtrlFx.cpp @@ -0,0 +1,372 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "ListCtrlFx.h" +#include "OsInfoFx.h" + +IMPLEMENT_DYNAMIC(CListCtrlFx, CListCtrl) + +CListCtrlFx::CListCtrlFx() +{ + m_X = 0; + m_Y = 0; + m_bNT6orLater = IsNT6orLater(); + m_BkDC = NULL; + m_bHighContrast = FALSE; + m_RenderMode = SystemDraw; + m_bDarkMode = FALSE; + + // Color + m_TextColor1 = RGB(0, 0, 0); + m_TextColor2 = RGB(0, 0, 0); + m_TextSelected = RGB(0, 0, 0); + m_BkColor1 = RGB(255, 255, 255); + m_BkColor2 = RGB(248, 248, 248); + m_BkSelected = RGB(248, 248, 255); + m_LineColor1 = RGB(224, 224, 224); + m_LineColor2 = RGB(240, 240, 240); + + // Glass + m_GlassColor = RGB(255, 255, 255); + m_GlassAlpha = 128; +} + +CListCtrlFx::~CListCtrlFx() +{ +} + +#pragma warning( disable : 26454 ) +BEGIN_MESSAGE_MAP(CListCtrlFx, CListCtrl) + ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, &CListCtrlFx::OnCustomdraw) +END_MESSAGE_MAP() +#pragma warning( default : 26454 ) + +BOOL CListCtrlFx::InitControl(int x, int y, int width, int height, int maxWidth, int maxHeight, double zoomRatio, CDC* bkDC, int renderMode, BOOL bHighContrast, BOOL bDarkMode) +{ + m_X = (int)(x * zoomRatio); + m_Y = (int)(y * zoomRatio); + m_CtrlSize.cx = (int)(width * zoomRatio); + m_CtrlSize.cy = (int)(height * zoomRatio); + MoveWindow(m_X, m_Y, m_CtrlSize.cx, m_CtrlSize.cy); + maxWidth = (int)(maxWidth * zoomRatio); + maxHeight = (int)(maxHeight * zoomRatio); + + m_BkDC = bkDC; + m_RenderMode = renderMode; + m_bHighContrast = bHighContrast; + m_bDarkMode = bDarkMode; + + if (m_bHighContrast) + { + SetBkImage((LPTSTR)_T("")); + } + else if (renderMode & OwnerDrawGlass || renderMode & OwnerDrawTransparent) + { + m_BkBitmap.DeleteObject(); + m_BkBitmap.CreateCompatibleBitmap(m_BkDC, maxWidth, maxHeight); + CDC BkDC; + BkDC.CreateCompatibleDC(m_BkDC); + BkDC.SelectObject(m_BkBitmap); + BkDC.BitBlt(0, 0, maxWidth, maxHeight, m_BkDC, m_X + 2, m_Y + 2, SRCCOPY); + + m_CtrlImage.Destroy(); + m_CtrlImage.Create(maxWidth, maxHeight, 32); + + RECT rect{}; + rect.top = 0; + rect.left = 0; + rect.right = maxWidth; + rect.bottom = maxHeight; + + m_CtrlBitmap.Detach(); + m_CtrlBitmap.Attach((HBITMAP)m_CtrlImage); + + DWORD length = maxWidth * maxHeight * 4; + BYTE* bitmapBits = new BYTE[length]; + m_CtrlBitmap.GetBitmapBits(length, bitmapBits); + + BYTE r, g, b, a; + if (renderMode & OwnerDrawGlass) + { + r = (BYTE)GetRValue(m_GlassColor); + g = (BYTE)GetGValue(m_GlassColor); + b = (BYTE)GetBValue(m_GlassColor); + a = m_GlassAlpha; + } + else // OwnerDrawTransparent + { + r = 0; + g = 0; + b = 0; + a = 0; + } + + for (int y = 0; y < maxHeight; y++) + { + for (int x = 0; x < maxWidth; x++) + { + DWORD p = (y * maxWidth + x) * 4; +#pragma warning( disable : 6386 ) + bitmapBits[p + 0] = b; + bitmapBits[p + 1] = g; + bitmapBits[p + 2] = r; + bitmapBits[p + 3] = a; +#pragma warning( default : 6386 ) + } + } + + m_CtrlBitmap.SetBitmapBits(length, bitmapBits); + delete[] bitmapBits; + + SetupControlImage(m_BkBitmap, m_CtrlBitmap); + if(m_bNT6orLater) + { + SetBkImage((HBITMAP)m_CtrlBitmap); + m_Header.InitControl(x, y, zoomRatio, bkDC, &m_CtrlBitmap, m_TextColor1, m_BkColor1, m_LineColor1, m_RenderMode, m_bHighContrast, m_bDarkMode); + } + else + { + SetBkColor(m_BkColor1); + m_Header.InitControl(x, y, zoomRatio, bkDC, NULL, m_TextColor1, m_BkColor1, m_LineColor1, m_RenderMode, m_bHighContrast, m_bDarkMode); + } + } + else + { + if(m_bNT6orLater) + { + SetBkImage((LPTSTR)_T("")); + SetBkColor(m_BkColor1); + m_Header.InitControl(x, y, zoomRatio, bkDC, NULL, m_TextColor1, m_BkColor1, m_LineColor1, m_RenderMode, m_bHighContrast, m_bDarkMode); + } + else + { + SetBkColor(m_BkColor1); + m_Header.InitControl(x, y, zoomRatio, bkDC, NULL, m_TextColor1, m_BkColor1, m_LineColor1, m_RenderMode, m_bHighContrast, m_bDarkMode); + } + } + + return TRUE; +} + +void CListCtrlFx::SetupControlImage(CBitmap& bkBitmap, CBitmap& ctrlBitmap) +{ + int color = m_BkDC->GetDeviceCaps(BITSPIXEL) * m_BkDC->GetDeviceCaps(PLANES); + + CBitmap* bk32Bitmap; + CImage bk32Image; + if (color == 32) + { + bk32Bitmap = &bkBitmap; + } + else + { + bk32Image.Create(m_CtrlSize.cx, m_CtrlSize.cy, 32); + ::BitBlt(bk32Image.GetDC(), 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, *m_BkDC, m_X, m_Y, SRCCOPY); + bk32Bitmap = CBitmap::FromHandle((HBITMAP)bk32Image); + bk32Image.ReleaseDC(); + } + + BITMAP CtlBmpInfo, DstBmpInfo; + bk32Bitmap->GetBitmap(&DstBmpInfo); + DWORD DstLineBytes = DstBmpInfo.bmWidthBytes; + DWORD DstMemSize = DstLineBytes * DstBmpInfo.bmHeight; + ctrlBitmap.GetBitmap(&CtlBmpInfo); + DWORD CtlLineBytes = CtlBmpInfo.bmWidthBytes; + DWORD CtlMemSize = CtlLineBytes * CtlBmpInfo.bmHeight; + BYTE* DstBuffer = new BYTE[DstMemSize]; + bk32Bitmap->GetBitmapBits(DstMemSize, DstBuffer); + BYTE* CtlBuffer = new BYTE[CtlMemSize]; + ctrlBitmap.GetBitmapBits(CtlMemSize, CtlBuffer); + + int baseY = 0; + for (LONG py = 0; py < DstBmpInfo.bmHeight; py++) + { + int dn = py * DstLineBytes; + int cn = (baseY + py) * CtlLineBytes; + for (LONG px = 0; px < DstBmpInfo.bmWidth; px++) + { +#pragma warning( disable : 6385 ) +#pragma warning( disable : 6386 ) + BYTE a = CtlBuffer[cn + 3]; + BYTE na = 255 - a; + CtlBuffer[dn + 0] = (BYTE)((CtlBuffer[cn + 0] * a + DstBuffer[dn + 0] * na) / 255); + CtlBuffer[dn + 1] = (BYTE)((CtlBuffer[cn + 1] * a + DstBuffer[dn + 1] * na) / 255); + CtlBuffer[dn + 2] = (BYTE)((CtlBuffer[cn + 2] * a + DstBuffer[dn + 2] * na) / 255); + dn += (DstBmpInfo.bmBitsPixel / 8); + cn += (CtlBmpInfo.bmBitsPixel / 8); +#pragma warning( default : 6386 ) +#pragma warning( default : 6385 ) + } + } + + if (color == 32) + { + ctrlBitmap.SetBitmapBits(CtlMemSize, CtlBuffer); + } + else + { + bk32Bitmap->SetBitmapBits(CtlMemSize, CtlBuffer); + m_CtrlImage.Detach(); + m_CtrlImage.Attach(ctrlBitmap); + ::BitBlt(m_CtrlImage.GetDC(), 0, 0, m_CtrlSize.cx - 1, m_CtrlSize.cy - 1, bk32Image.GetDC(), 1, 1, SRCCOPY); + m_CtrlImage.ReleaseDC(); + bk32Image.ReleaseDC(); + } + + delete[] DstBuffer; + delete[] CtlBuffer; +} + +void CListCtrlFx::OnCustomdraw(NMHDR *pNMHDR, LRESULT *pResult) +{ + LPNMLVCUSTOMDRAW lpLVCustomDraw = reinterpret_cast(pNMHDR); + + switch(lpLVCustomDraw->nmcd.dwDrawStage) + { + case CDDS_ITEMPREPAINT: + case CDDS_ITEMPREPAINT | CDDS_SUBITEM: + if(! m_bHighContrast) + { + if(lpLVCustomDraw->nmcd.dwItemSpec % 2 == 0) + { + lpLVCustomDraw->clrText = m_TextColor1; + lpLVCustomDraw->clrTextBk = m_BkColor1; + } + else + { + lpLVCustomDraw->clrText = m_TextColor2; + lpLVCustomDraw->clrTextBk = m_BkColor2; + } + } + break; + case CDDS_ITEMPOSTPAINT | CDDS_SUBITEM: + { + RECT rc{}; + CBrush br1(m_LineColor1); + CBrush br2(m_LineColor2); + + CHeaderCtrl* header = this->GetHeaderCtrl(); + if(header != NULL) + { + int count = header->GetItemCount(); + for(int i = 0; i < count; i++) + { + ListView_GetSubItemRect(m_hWnd, lpLVCustomDraw->nmcd.dwItemSpec, i, LVIR_LABEL, &rc); + LONG left = rc.left; + rc.left = rc.right - 1; + FillRect(lpLVCustomDraw->nmcd.hdc, &rc, (HBRUSH)br1.GetSafeHandle()); + rc.left = left; + rc.top = rc.bottom - 1; + FillRect(lpLVCustomDraw->nmcd.hdc, &rc, (HBRUSH)br2.GetSafeHandle()); + } + } + } + break; + default: + break; + } + + *pResult = 0; + *pResult |= CDRF_NOTIFYPOSTPAINT; + *pResult |= CDRF_NOTIFYITEMDRAW; + *pResult |= CDRF_NOTIFYSUBITEMDRAW; +} + +void CListCtrlFx::SetTextColor1(COLORREF color){m_TextColor1 = color;} +void CListCtrlFx::SetTextColor2(COLORREF color){m_TextColor2 = color;} +void CListCtrlFx::SetTextSelected(COLORREF color){m_TextSelected = color;} +void CListCtrlFx::SetBkColor1(COLORREF color){m_BkColor1 = color;} +void CListCtrlFx::SetBkColor2(COLORREF color){m_BkColor2 = color;} +void CListCtrlFx::SetBkSelected(COLORREF color){m_BkSelected = color;} +void CListCtrlFx::SetLineColor1(COLORREF color){m_LineColor1 = color;} +void CListCtrlFx::SetLineColor2(COLORREF color){m_LineColor2 = color;} + +void CListCtrlFx::SetGlassColor(COLORREF glassColor, BYTE glassAlpha) +{ + m_GlassColor = glassColor; + m_GlassAlpha = glassAlpha; +} + +COLORREF CListCtrlFx::GetTextColor1(){return m_TextColor1;} +COLORREF CListCtrlFx::GetTextColor2(){return m_TextColor2;} +COLORREF CListCtrlFx::GetTextSelected(){return m_TextSelected;} +COLORREF CListCtrlFx::GetBkColor1(){return m_BkColor1;} +COLORREF CListCtrlFx::GetBkColor2(){return m_BkColor2;} +COLORREF CListCtrlFx::GetBkSelected(){return m_BkSelected;} +COLORREF CListCtrlFx::GetLineColor1(){return m_LineColor1;} +COLORREF CListCtrlFx::GetLineColor2(){return m_LineColor2;} + +void CListCtrlFx::SetFontEx(CString face, int size, double zoomRatio, double fontRatio, LONG fontWeight, BYTE fontRender) +{ + LOGFONT logFont = { 0 }; + logFont.lfCharSet = DEFAULT_CHARSET; + logFont.lfHeight = (LONG)(-1 * size * zoomRatio * fontRatio); + logFont.lfQuality = fontRender; + logFont.lfWeight = fontWeight; + if (face.GetLength() < 32) + { + wsprintf(logFont.lfFaceName, _T("%s"), (LPCTSTR)face); + } + else + { + wsprintf(logFont.lfFaceName, _T("")); + } + + m_Font.DeleteObject(); + m_Font.CreateFontIndirect(&logFont); + SetFont(&m_Font); + + m_Header.SetFontEx(face, size, zoomRatio, fontRatio, fontWeight, fontRender); +} + +void CListCtrlFx::PreSubclassWindow() +{ + CListCtrl::PreSubclassWindow(); + + CHeaderCtrlFx* pHeader = (CHeaderCtrlFx*)GetHeaderCtrl(); + m_Header.SubclassWindow(pHeader->GetSafeHwnd()); +} + +void CListCtrlFx::EnableHeaderOwnerDraw(BOOL bOwnerDraw) +{ + if (m_bHighContrast) + { + HDITEM hi = { 0 }; + hi.mask = HDI_FORMAT; + for (int i = 0; i < m_Header.GetItemCount(); i++) + { + m_Header.GetItem(i, &hi); + hi.fmt &= ~HDF_OWNERDRAW; + m_Header.SetItem(i, &hi); + } + return; + } + else if (m_RenderMode & OwnerDrawGlass) + { + HDITEM hi = {0}; + hi.mask = HDI_FORMAT; + if (bOwnerDraw) + { + for (int i = 0; i < m_Header.GetItemCount(); i++) + { + m_Header.GetItem(i, &hi); + hi.fmt |= HDF_OWNERDRAW; + m_Header.SetItem(i, &hi); + } + } + else + { + for (int i = 0; i < m_Header.GetItemCount(); i++) + { + m_Header.GetItem(i, &hi); + hi.fmt &= ~HDF_OWNERDRAW; + m_Header.SetItem(i, &hi); + } + } + } +} diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/ListCtrlFx.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ListCtrlFx.h new file mode 100644 index 0000000..315f420 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ListCtrlFx.h @@ -0,0 +1,85 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once + +#include "HeaderCtrlFx.h" +#include "ImageFx.h" + +class CListCtrlFx : public CListCtrl +{ + DECLARE_DYNAMIC(CListCtrlFx) + +public: + CListCtrlFx(); + virtual ~CListCtrlFx(); + + void SetTextColor1(COLORREF color); + void SetTextColor2(COLORREF color); + void SetTextSelected(COLORREF color); + void SetBkColor1(COLORREF color); + void SetBkColor2(COLORREF color); + void SetBkSelected(COLORREF color); + void SetLineColor1(COLORREF color); + void SetLineColor2(COLORREF color); + void SetGlassColor(COLORREF glassColor, BYTE glassAlpha); + + COLORREF GetTextColor1(); + COLORREF GetTextColor2(); + COLORREF GetTextSelected(); + COLORREF GetBkColor1(); + COLORREF GetBkColor2(); + COLORREF GetBkSelected(); + COLORREF GetLineColor1(); + COLORREF GetLineColor2(); + + BOOL InitControl(int x, int y, int width, int height, int maxWidth, int maxHeight, double zoomRatio, CDC* bkDC, int renderMode, BOOL bHighContrast, BOOL bDarkMode); + void SetFontEx(CString face, int size, double zoomRatio, double fontRatio, LONG fontWeight, BYTE fontRender); + void EnableHeaderOwnerDraw(BOOL bOwnerDraw); + +protected: + virtual void PreSubclassWindow(); + + void SetupControlImage(CBitmap& bkBitmap, CBitmap& ctrlBitmap); + + DECLARE_MESSAGE_MAP() + afx_msg void OnCustomdraw(NMHDR* pNMHDR, LRESULT* pResult); + + int m_X; + int m_Y; + BOOL m_bNT6orLater; + CSize m_CtrlSize; + CRect m_Margin; + int m_RenderMode; + BOOL m_bHighContrast; + BOOL m_bDarkMode; + CHeaderCtrlFx m_Header; + + COLORREF m_TextColor1; + COLORREF m_TextColor2; + COLORREF m_TextSelected; + COLORREF m_BkColor1; + COLORREF m_BkColor2; + COLORREF m_BkSelected; + COLORREF m_LineColor1; + COLORREF m_LineColor2; + + CFont m_Font; + CImageList m_Image; + CDC* m_BkDC; + + // Glass + COLORREF m_GlassColor; + BYTE m_GlassAlpha; + + // Image + CBitmap m_BkBitmap; + CBitmap m_CtrlBitmap; + CImage m_CtrlImage; +}; + + diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/MainDialogFx.cpp b/CristalDiskMark/source/CrystalDiskMark/Priscilla/MainDialogFx.cpp new file mode 100644 index 0000000..bbf780d --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/MainDialogFx.cpp @@ -0,0 +1,1070 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "MainDialogFx.h" +#include +#include +#include "UtilityFx.h" +#include "OsInfoFx.h" +using namespace std; + +CMainDialogFx::CMainDialogFx(UINT dlgResouce, CWnd* pParent) + :CDialogFx(dlgResouce, pParent) +{ + // Common + m_bStartup = FALSE; + m_bWindowMinimizeOnce = TRUE; + m_bResident = FALSE; + m_bResidentMinimize = FALSE; + + // Theme + m_ThemeKeyName = _T("Theme"); + + TCHAR* ptrEnd; + TCHAR ini[MAX_PATH]; + TCHAR tmp[MAX_PATH]; + CString directry; + + + GetModuleFileName(NULL, ini, MAX_PATH); + if ((ptrEnd = _tcsrchr(ini, '.')) != NULL) + { + *ptrEnd = '\0'; +#if _MSC_VER > 1310 + _tcscat_s(ini, MAX_PATH, _T(".ini")); +#else + _tcscat(ini, _T(".ini")); +#endif + m_Ini = ini; + } + +#if _MSC_VER > 1310 + CString tmpPath; + tmpPath = m_Ini; + tmpPath.Replace(_T(".ini"), _T(".tmp")); + + if (! CanWriteFile(tmpPath)) + { + TCHAR drive[_MAX_DRIVE]; + TCHAR ext[_MAX_EXT]; + TCHAR appData[MAX_PATH]; + TCHAR dir[_MAX_DIR]; + TCHAR fileName[_MAX_FNAME]; + GetModuleFileName(NULL, ini, MAX_PATH); + _tsplitpath(ini, drive, dir, fileName, ext); + SHGetSpecialFolderPath(NULL, appData, CSIDL_APPDATA, 0); + directry.Format(_T("%s\\%s"), appData, PRODUCT_FILENAME); + CreateDirectory(directry, NULL); + m_Ini.Format(_T("%s\\%s\\%s.ini"), appData, PRODUCT_FILENAME, fileName); + } +#endif + + GetModuleFileName(NULL, tmp, MAX_PATH); + if ((ptrEnd = _tcsrchr(tmp, '\\')) != NULL) { *ptrEnd = '\0'; } + m_ThemeDir.Format(_T("%s\\%s"), tmp, THEME_DIR); + m_LangDir.Format(_T("%s\\%s"), tmp, LANGUAGE_DIR); + m_VoiceDir.Format(_T("%s\\%s"), tmp, VOICE_DIR); +} + +CMainDialogFx::~CMainDialogFx() +{ +} + +BEGIN_MESSAGE_MAP(CMainDialogFx, CDialogFx) + ON_WM_WINDOWPOSCHANGING() + ON_WM_GETMINMAXINFO() +END_MESSAGE_MAP() + + +LRESULT CMainDialogFx::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) +{ +#if _MSC_VER > 1310 + LRESULT lr = 0; + if (UAHWndProc(m_hWnd, message, wParam, lParam, &lr)) { + return lr; + } +#endif + + return CDialogFx::WindowProc(message, wParam, lParam); +} + +int CALLBACK HasFontProc(ENUMLOGFONTEX* lpelfe, NEWTEXTMETRICEX* lpntme, int FontType, LPARAM lParam) +{ + *(BOOL*)lParam = TRUE; + return 0; +} + +CString CMainDialogFx::GetDefaultFont() +{ +#if _MSC_VER <= 1310 + int stockFont = DEFAULT_GUI_FONT; + if (IsNT3()) + { + return _T("MS Shell Dlg"); + } + HFONT hFont = (HFONT)GetStockObject(stockFont); + LOGFONT lf = { 0 }; + + if (GetObject(hFont, sizeof(LOGFONT), &lf)) + { + return lf.lfFaceName; + } +#endif + + CClientDC dc(this); + LOGFONT logfont; + BOOL hasFont = FALSE; + ZeroMemory(&logfont, sizeof(LOGFONT)); + lstrcpy(logfont.lfFaceName, DEFAULT_FONT_FACE_1); + logfont.lfCharSet = DEFAULT_CHARSET; + ::EnumFontFamiliesEx(dc.m_hDC, &logfont, (FONTENUMPROC)HasFontProc, (LPARAM)(&hasFont), 0); + + if (hasFont) + { + return DEFAULT_FONT_FACE_1; + } + else + { + return DEFAULT_FONT_FACE_2; + } +} + +CString CMainDialogFx::GetCurrentLangPath() +{ + return m_CurrentLangPath; +} + +CString CMainDialogFx::GetDefaultLangPath() +{ + return m_DefaultLangPath; +} + +CString CMainDialogFx::GetThemeDir() +{ + return m_ThemeDir; +} + +CString CMainDialogFx::GetCurrentTheme() +{ + return m_CurrentTheme; +} + +CString CMainDialogFx::GetDefaultTheme() +{ + return m_DefaultTheme; +} + +CString CMainDialogFx::GetParentTheme1() +{ + return m_ParentTheme1; +} + +CString CMainDialogFx::GetParentTheme2() +{ + return m_ParentTheme2; +} + +CString CMainDialogFx::GetIniPath() +{ + return m_Ini; +} + +void CMainDialogFx::SetWindowTitle(CString message) +{ + CString title; + + if(! message.IsEmpty()) + { + title.Format(_T(" %s - %s"), PRODUCT_SHORT_NAME, (LPCTSTR)message); + } + else + { + title.Format(_T(" %s %s %s"), PRODUCT_NAME, PRODUCT_VERSION, PRODUCT_EDITION); + } + + SetWindowText(title); +} + +void CMainDialogFx::InitThemeLang() +{ + TCHAR str[256]; + TCHAR *ptrEnd; + + WIN32_FIND_DATA findData; + HANDLE hFind; + CString langPath; + int i = 0; + WORD PrimaryLangID; + CString PrimaryLang; + +// Set Theme + if(m_CurrentTheme.IsEmpty()) + { + CString defaultTheme = m_DefaultTheme; +#if _MSC_VER > 1310 + if (IsFileExist(m_ThemeDir + m_RecommendTheme + _T("\\") + m_BackgroundName + _T("-300.png"))) + { + defaultTheme = m_RecommendTheme; + } +#else + if (IsFileExist(m_ThemeDir + m_RecommendTheme + _T("\\") + m_BackgroundName + _T("-100.png"))) + { + defaultTheme = m_RecommendTheme; + } +#endif + + GetPrivateProfileStringFx(_T("Setting"), m_ThemeKeyName, defaultTheme, str, 256, m_Ini); + m_CurrentTheme = str; + } + +// Set Language + GetPrivateProfileStringFx(_T("Setting"), _T("Language"), _T(""), str, 256, m_Ini); + + langPath.Format(_T("%s\\%s.lang"), (LPCTSTR)m_LangDir, str); +#ifdef UNICODE + m_DefaultLangPath.Format(_T("%s\\%s.lang"), (LPCTSTR)m_LangDir, DEFAULT_LANGUAGE); +#else + m_DefaultLangPath.Format(_T("%s\\%s9x.lang"), (LPCTSTR)m_LangDir, DEFAULT_LANGUAGE); +#endif + if(_tcscmp(str, _T("")) != 0 && IsFileExist((LPCTSTR)langPath)) + { + m_CurrentLang = str; + m_CurrentLang.Replace(_T("9x"), _T("")); +#ifdef UNICODE + m_CurrentLangPath.Format(_T("%s\\%s.lang"), (LPCTSTR)m_LangDir, (LPCTSTR)m_CurrentLang); +#else + m_CurrentLangPath.Format(_T("%s\\%s9x.lang"), (LPCTSTR)m_LangDir, (LPCTSTR)m_CurrentLang); +#endif + } + else + { + CString currentLocalID; + currentLocalID.Format(_T("0x%04X"), GetUserDefaultLCID()); + PrimaryLangID = PRIMARYLANGID(GetUserDefaultLCID()); + + + langPath.Format(_T("%s\\*.lang"), (LPCTSTR)m_LangDir); + + hFind = ::FindFirstFile(langPath, &findData); + if(hFind != INVALID_HANDLE_VALUE) + { + do{ + if(findData.dwFileAttributes != FILE_ATTRIBUTE_DIRECTORY) + { + + CString cstr; + cstr = findData.cFileName; + +#ifdef UNICODE + if (cstr.Find(_T("9x.lang")) >= 0) + { + continue; + } +#else + if (cstr.Find(_T("9x.lang")) == -1) + { + continue; + } +#endif + i++; + + cstr.Format(_T("%s\\%s"), (LPCTSTR)m_LangDir, findData.cFileName); + GetPrivateProfileStringFx(_T("Language"), _T("LOCALE_ID"), _T(""), str, 256, cstr); + if((ptrEnd = _tcsrchr(findData.cFileName, '.')) != NULL){*ptrEnd = '\0';} + + if(_tcsstr(str, currentLocalID) != NULL) + { + m_CurrentLang = findData.cFileName; + m_CurrentLang.Replace(_T("9x"), _T("")); +#ifdef UNICODE + m_CurrentLangPath.Format(_T("%s\\%s.lang"), (LPCTSTR)m_LangDir, findData.cFileName); +#else + m_CurrentLangPath.Format(_T("%s\\%s9x.lang"), (LPCTSTR)m_LangDir, findData.cFileName); +#endif + } + if(PrimaryLangID == PRIMARYLANGID(_tcstol(str, NULL, 16))) + { + PrimaryLang = findData.cFileName; + PrimaryLang.Replace(_T("9x"), _T("")); + } + } + }while(::FindNextFile(hFind, &findData) && i <= 0xFF); + } + FindClose(hFind); + + if(m_CurrentLang.IsEmpty()) + { + if(PrimaryLang.IsEmpty()) + { + m_CurrentLang = DEFAULT_LANGUAGE; +#ifdef UNICODE + m_CurrentLangPath.Format(_T("%s\\%s.lang"), (LPCTSTR)m_LangDir, (LPCTSTR)m_CurrentLang); +#else + m_CurrentLangPath.Format(_T("%s\\%s9x.lang"), (LPCTSTR)m_LangDir, (LPCTSTR)m_CurrentLang); +#endif + } + else + { + m_CurrentLang = PrimaryLang; +#ifdef UNICODE + m_CurrentLangPath.Format(_T("%s\\%s.lang"), (LPCTSTR)m_LangDir, (LPCTSTR)PrimaryLang); +#else + m_CurrentLangPath.Format(_T("%s\\%s9x.lang"), (LPCTSTR)m_LangDir, (LPCTSTR)PrimaryLang); +#endif + } + } + } + + UpdateThemeInfo(); +} + +void CMainDialogFx::InitMenu() +{ + CMenu menu; + CMenu subMenu; + BOOL FlagHitTheme = FALSE; + BOOL FlagHitLang = FALSE; + UINT newItemID = 0; + UINT currentItemID = 0; + UINT defaultStyleItemID = 0; + WIN32_FIND_DATA findData; + + HANDLE hFind; + CString themePath; + CString themeCssPath; + CString langPath; + int i = 0; + TCHAR *ptrEnd = NULL; + TCHAR str[256]; + + srand((unsigned int)std::time(NULL)); + + menu.Attach(GetMenu()->GetSafeHmenu()); + subMenu.Attach(menu.GetSubMenu(MENU_THEME_INDEX)->GetSafeHmenu()); + + themePath.Format(_T("%s\\*.*"), (LPCTSTR)m_ThemeDir); + + // Add Random as first choice. + subMenu.AppendMenu(MF_STRING, (UINT_PTR)WM_THEME_ID + i, m_RandomThemeLabel + m_RandomThemeName); + i++; + m_MenuArrayTheme.Add(m_RandomThemeLabel); + + hFind = ::FindFirstFile(themePath, &findData); + if(hFind != INVALID_HANDLE_VALUE) + { + while(::FindNextFile(hFind, &findData) && i <= 0xFF) + { + if(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + CString name = findData.cFileName; + if(CheckThemeEdition(name)) + { + // Add Theme + newItemID = WM_THEME_ID + i; + i++; + subMenu.AppendMenu(MF_STRING, (UINT_PTR)newItemID, findData.cFileName); + m_MenuArrayTheme.Add(findData.cFileName); + if(m_CurrentTheme.Compare(findData.cFileName) == 0) + { + currentItemID = newItemID; + FlagHitTheme = TRUE; + } + + if(_tcsstr(findData.cFileName, m_DefaultTheme) != NULL) + { + defaultStyleItemID = newItemID; + } + } + } + } + } + FindClose(hFind); + + if(m_CurrentTheme.Compare(m_RandomThemeLabel) == 0) + { + m_CurrentTheme = GetRandomTheme(); + m_RandomThemeName = _T(" (") + m_CurrentTheme + _T(")"); + // Keep currentItemID the same as the first item if "Random". + currentItemID = WM_THEME_ID; + + SUBMENU_MODIFY_MENU(WM_THEME_ID, MF_STRING, WM_THEME_ID, m_RandomThemeLabel + m_RandomThemeName); + } + else if(! FlagHitTheme) + { + currentItemID = defaultStyleItemID; + m_CurrentTheme = m_DefaultTheme; + } + + CMenu subMenuAN; + CMenu subMenuOZ; + subMenu.CheckMenuRadioItem(WM_THEME_ID, WM_THEME_ID + (UINT)m_MenuArrayTheme.GetSize(), + currentItemID, MF_BYCOMMAND); + subMenu.Detach(); + subMenu.Attach(menu.GetSubMenu(MENU_LANG_INDEX)->GetSafeHmenu()); + subMenuAN.Attach(subMenu.GetSubMenu(0)->GetSafeHmenu()); // 1st is "A~N" + subMenuAN.RemoveMenu(0, MF_BYPOSITION); + subMenuOZ.Attach(subMenu.GetSubMenu(1)->GetSafeHmenu()); // 2nd is "O~Z" + subMenuOZ.RemoveMenu(0, MF_BYPOSITION); + langPath.Format(_T("%s\\*.lang"), (LPCTSTR)m_LangDir); + i = 0; + hFind = ::FindFirstFile(langPath, &findData); + if(hFind != INVALID_HANDLE_VALUE) + { + do{ + if(findData.dwFileAttributes != FILE_ATTRIBUTE_DIRECTORY) + { + // Add Language + CString cstr; + cstr = findData.cFileName; +#ifdef UNICODE + if (cstr.Find(_T("9x.lang")) >= 0) + { + continue; + } +#else + if (cstr.Find(_T("9x.lang")) == -1) + { + continue; + } +#endif + newItemID = WM_LANGUAGE_ID + i; + i++; + + cstr.Format(_T("%s\\%s"), (LPCTSTR)m_LangDir, findData.cFileName); + GetPrivateProfileStringFx(_T("Language"), _T("LANGUAGE"), _T(""), str, 256, cstr); + if((ptrEnd = _tcsrchr(findData.cFileName, L'.')) != NULL) + { + *ptrEnd = '\0'; + } + + cstr.Format(_T("%s, [%s]"), str, findData.cFileName); + if(L'A' <= findData.cFileName[0] && findData.cFileName[0] <= L'N') + { + subMenuAN.AppendMenu(MF_STRING, (UINT_PTR)newItemID, cstr); + } + else + { + subMenuOZ.AppendMenu(MF_STRING, (UINT_PTR)newItemID, cstr); + } + m_MenuArrayLang.Add(findData.cFileName); + + cstr = findData.cFileName; +#ifndef UNICODE + cstr.Replace(_T("9x"), _T("")); +#endif + if(m_CurrentLang.Compare(cstr) == 0) + { + currentItemID = newItemID; + FlagHitLang = TRUE; + } + } + }while(::FindNextFile(hFind, &findData) && i <= 0xFF); + } + FindClose(hFind); + + subMenuAN.CheckMenuRadioItem(WM_LANGUAGE_ID, WM_LANGUAGE_ID + (UINT)m_MenuArrayLang.GetSize(), + currentItemID, MF_BYCOMMAND); + subMenuOZ.CheckMenuRadioItem(WM_LANGUAGE_ID, WM_LANGUAGE_ID + (UINT)m_MenuArrayLang.GetSize(), + currentItemID, MF_BYCOMMAND); + + subMenuOZ.Detach(); + subMenuAN.Detach(); + subMenu.Detach(); + menu.Detach(); + + if(! FlagHitLang) + { + AfxMessageBox(_T("FATAL ERROR: Missing Language File!!")); + } +} + +BOOL CMainDialogFx::CheckThemeEdition(CString name) +{ + if (name.Find(_T(".")) != 0) { return TRUE; } + + return FALSE; +} + +BOOL CMainDialogFx::OnInitDialog() +{ + return CDialogFx::OnInitDialog(); +} + +void CMainDialogFx::ChangeTheme(CString themeName) +{ + WritePrivateProfileStringFx(_T("Setting"), m_ThemeKeyName, themeName, m_Ini); +} + +BOOL CMainDialogFx::OnCommand(WPARAM wParam, LPARAM lParam) +{ + // Select Theme + if(WM_THEME_ID <= wParam && wParam < WM_THEME_ID + (WPARAM)m_MenuArrayTheme.GetSize()) + { + CMenu menu; + CMenu subMenu; + menu.Attach(GetMenu()->GetSafeHmenu()); + subMenu.Attach(menu.GetSubMenu(MENU_THEME_INDEX)->GetSafeHmenu()); + + m_CurrentTheme = m_MenuArrayTheme.GetAt(wParam - WM_THEME_ID); + if (m_CurrentTheme.Compare(m_RandomThemeLabel) == 0) + { + m_CurrentTheme = GetRandomTheme(); + m_RandomThemeLabel = _T("Random"); + m_RandomThemeName = _T(" (") + m_CurrentTheme + _T(")"); + + // ChangeTheme save the theme configuration to profile; so if we are on + // Random, then save Random to profile. + ChangeTheme(m_RandomThemeLabel); + } + else + { + ChangeTheme(m_MenuArrayTheme.GetAt(wParam - WM_THEME_ID)); + m_RandomThemeName = _T(""); + } + + SUBMENU_MODIFY_MENU(WM_THEME_ID, MF_STRING, WM_THEME_ID, m_RandomThemeLabel + m_RandomThemeName); + subMenu.CheckMenuRadioItem(WM_THEME_ID, WM_THEME_ID + (UINT)m_MenuArrayTheme.GetSize(), + (UINT)wParam, MF_BYCOMMAND); + subMenu.Detach(); + menu.Detach(); + + UpdateThemeInfo(); + UpdateDialogSize(); + } + + return CDialogFx::OnCommand(wParam, lParam); +} + +void CMainDialogFx::OnWindowPosChanging(WINDOWPOS * lpwndpos) +{ + if(! m_bShowWindow) + { + lpwndpos->flags &= ~SWP_SHOWWINDOW; + } + + if(m_bWindowMinimizeOnce && ! m_bInitializing) + { + m_bWindowMinimizeOnce = FALSE; + if(m_bResident && m_bResidentMinimize) + { + ShowWindow(SW_MINIMIZE); + } + } + + CDialogFx::OnWindowPosChanging(lpwndpos); +} + +void CMainDialogFx::OnGetMinMaxInfo(MINMAXINFO* lpMMI) +{ + lpMMI->ptMinTrackSize.x = m_MinSizeX; + lpMMI->ptMinTrackSize.y = m_MinSizeY; + + lpMMI->ptMaxTrackSize.x = m_MaxSizeX; + lpMMI->ptMaxTrackSize.y = m_MaxSizeY; + + CDialogFx::OnGetMinMaxInfo(lpMMI); +} + +void CMainDialogFx::SaveWindowPosition() +{ + WINDOWPLACEMENT place = { sizeof(WINDOWPLACEMENT) }; + GetWindowPlacement(&place); + + CString x, y; + x.Format(_T("%d"), place.rcNormalPosition.left); + y.Format(_T("%d"), place.rcNormalPosition.top); + WritePrivateProfileStringFx(_T("Setting"), _T("X"), x, m_Ini); + WritePrivateProfileStringFx(_T("Setting"), _T("Y"), y, m_Ini); +} + +void CMainDialogFx::RestoreWindowPosition() +{ + const int x = GetPrivateProfileInt(_T("Setting"), _T("X"), INT_MIN, m_Ini); + const int y = GetPrivateProfileInt(_T("Setting"), _T("Y"), INT_MIN, m_Ini); + + RECT rw, rc; + GetWindowRect(&rw); + + rc.left = x; + rc.top = y; + rc.right = x + rw.right - rw.left; + rc.bottom = y + rw.bottom - rw.top; + +#if _MSC_VER > 1310 + HMONITOR hMonitor = MonitorFromRect(&rc, MONITOR_DEFAULTTONULL); + if (hMonitor == NULL) + { + CenterWindow(); + } + else + { + // Get Taskbar Size + APPBARDATA taskbarInfo = { 0 }; + taskbarInfo.cbSize = sizeof(APPBARDATA); + taskbarInfo.hWnd = m_hWnd; + SHAppBarMessage(ABM_GETTASKBARPOS, &taskbarInfo); + CRect taskbarRect = taskbarInfo.rc; + + if (taskbarInfo.rc.top <= 0 && taskbarInfo.rc.left <= 0) // Top Side or Left Side + { + if (taskbarRect.Height() > taskbarRect.Width()) // Left Side + { + if (x < taskbarRect.Width()) // Overlap + { + SetWindowPos(NULL, taskbarRect.Width(), y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); + } + else + { + SetWindowPos(NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); + } + } + else // Top Side + { + if (y < taskbarRect.Height()) // Overlap + { + SetWindowPos(NULL, x, taskbarRect.Height(), 0, 0, SWP_NOSIZE | SWP_NOZORDER); + } + else + { + SetWindowPos(NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); + } + } + } + else + { + SetWindowPos(NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); + } + } +#else + CenterWindow(); +#endif +} + +DWORD CMainDialogFx::GetZoomType() +{ + return GetPrivateProfileInt(_T("Setting"), _T("ZoomType"), ZoomTypeAuto, m_Ini); +} + +void CMainDialogFx::SetZoomType(DWORD zoomType) +{ + CString cstr; + cstr.Format(_T("%d"), m_ZoomType); + WritePrivateProfileStringFx(_T("Setting"), _T("ZoomType"), cstr, m_Ini); +} + +void CMainDialogFx::UpdateThemeInfo() +{ + CString theme = m_ThemeDir + m_CurrentTheme + _T("\\theme.ini"); + + m_LabelText = GetControlColor(_T("LabelText"), 0, theme); + m_MeterText = GetControlColor(_T("MeterText"), 0, theme); + m_ComboText = GetControlColor(_T("ComboText"), 0, theme); + m_ComboTextSelected = GetControlColor(_T("ComboTextSelected"), 0, theme); + m_ComboBk = GetControlColor(_T("ComboBk"), 255, theme); + m_ComboBkSelected = GetControlColor(_T("ComboBkSelected"), 192, theme); + m_ButtonText= GetControlColor(_T("ButtonText"), 0, theme); + m_EditText = GetControlColor(_T("EditText"), 0, theme); + m_EditBk = GetControlColor(_T("EditBk"), 255, theme); + m_ListText1 = GetControlColor(_T("ListText1"), 0, theme); + m_ListText2 = GetControlColor(_T("ListText2"), 0, theme); + m_ListTextSelected = GetControlColor(_T("ListTextSelected"), 0, theme); + m_ListBk1 = GetControlColor(_T("ListBk1"), 255, theme); + m_ListBk2 = GetControlColor(_T("ListBk2"), 255, theme); + m_ListBkSelected = GetControlColor(_T("ListBkSelected"), 0, theme); + m_ListLine1 = GetControlColor(_T("ListLine1"), 0, theme); + m_ListLine2 = GetControlColor(_T("ListLine2"), 0, theme); + m_Glass = GetControlColor(_T("Glass"), 255, theme); + m_Frame = GetControlColor(_T("Frame"), 128, theme); + m_Background = GetBackgroundColor(_T("Background"), theme); + + m_ComboAlpha = GetControlAlpha(_T("ComboAlpha"), 255, theme); + m_EditAlpha = GetControlAlpha(_T("EditAlpha"), 255, theme); + m_GlassAlpha = GetControlAlpha(_T("GlassAlpha"), 128, theme); + + m_CharacterPosition = GetCharacterPosition(theme); + + m_ParentTheme1 = GetParentTheme(1, theme); + m_ParentTheme2 = GetParentTheme(2, theme); +} + +COLORREF CMainDialogFx::GetControlColor(CString name, BYTE defaultColor, CString theme) +{ + COLORREF reverseColor; + + reverseColor = GetPrivateProfileInt(_T("Color"), name, RGB(defaultColor, defaultColor, defaultColor), theme); + + COLORREF color = RGB(GetBValue(reverseColor), GetGValue(reverseColor), GetRValue(reverseColor)); + + return color; +} + +COLORREF CMainDialogFx::GetBackgroundColor(CString name, CString theme) +{ + COLORREF reverseColor; + + reverseColor = GetPrivateProfileInt(_T("Color"), name, 0xFFFFFFFF, theme); + + if (reverseColor == 0xFFFFFFFF) + { + return 0xFFFFFFFF; // 0xFFFFFFF = Disabled + } + else + { + COLORREF color = RGB(GetBValue(reverseColor), GetGValue(reverseColor), GetRValue(reverseColor)); + return color; + } +} + +BYTE CMainDialogFx::GetControlAlpha(CString name, BYTE defaultAlpha, CString theme) +{ + BYTE alpha = (BYTE)GetPrivateProfileInt(_T("Alpha"), name, defaultAlpha, theme); + + return alpha; +} + +BYTE CMainDialogFx::GetCharacterPosition(CString theme) +{ + BYTE position = (BYTE)GetPrivateProfileInt(_T("Character"), _T("Position"), 0, theme); + + return position; +} + +CString CMainDialogFx::GetParentTheme(int i, CString theme) +{ + CString cstr; + cstr.Format(_T("ParentTheme%d"), i); + TCHAR str[256]; + GetPrivateProfileStringFx(_T("Info"), cstr, _T(""), str, 256, theme); + cstr = str; + + return cstr; +} + +CString CMainDialogFx::GetRandomTheme() { + // We need to add/subtract one to exclude first item ("Random"). + UINT i = 1 + rand() % ((UINT)m_MenuArrayTheme.GetSize() - 1); + return m_MenuArrayTheme.GetAt(i); +} + +void CMainDialogFx::SaveImage() +{ +#if _MSC_VER > 1310 + BOOL bDwmEnabled = FALSE; + + HMODULE hModule = LoadLibraryEx(_T("dwmapi.dll"), NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + typedef HRESULT(WINAPI* FuncDwmGetWindowAttribute) (HWND hwnd, DWORD dwAttribute, PVOID pvAttribute, DWORD cbAttribute); + typedef HRESULT(WINAPI* FuncDwmIsCompositionEnabled)(BOOL* pfEnabled); + FuncDwmGetWindowAttribute pDwmGetWindowAttribute = NULL; + FuncDwmIsCompositionEnabled pDwmIsCompositionEnabled = NULL; + if (hModule) + { + pDwmGetWindowAttribute = (FuncDwmGetWindowAttribute)GetProcAddress(hModule, "DwmGetWindowAttribute"); + pDwmIsCompositionEnabled = (FuncDwmIsCompositionEnabled)GetProcAddress(hModule, "DwmIsCompositionEnabled"); + } + + if (pDwmGetWindowAttribute && pDwmIsCompositionEnabled) + { + pDwmIsCompositionEnabled(&bDwmEnabled); + } + + CRect rc1; + CRect rc2; + CRect rc3; + + CImage* image1 = new CImage(); + CImage* image2 = new CImage(); + CImage* image3 = new CImage(); + + if (bDwmEnabled) + { + GetWindowRect(&rc1); + if (image1->Create(rc1.Width(), rc1.Height(), 32)) + { + HDC hImage1DC = image1->GetDC(); + if (IsWin81orLater()) + { + ::PrintWindow(m_hWnd, hImage1DC, 2); // PW_RENDERFULLCONTENT, Windows 8.1 or later + + GetClientRect(&rc3); + if (image3->Create(rc3.Width(), rc3.Height(), 32)) + { + HDC hImage3DC = image3->GetDC(); + ::PrintWindow(m_hWnd, hImage3DC, 1); // PW_CLIENTONLY + int targetY = 0; + int offsetX = (rc1.Width() - rc3.Width()) / 2; + int offsetY = (rc1.Height() - rc3.Height()) / 2; + + // Compare Screenshot + for (int y = offsetY; y < rc1.Height() - rc3.Height(); y++) + { + for (int x = 0; x < rc3.Width(); x++) + { + if (image1->GetPixel(offsetX + x, y) == image3->GetPixel(x, 0)) + { + if (x == rc3.Width() / 2) + { + for (int yy = 0; yy < rc3.Height(); yy++) + { + if (image1->GetPixel(offsetX, y + yy) == image3->GetPixel(0, yy)) + { + if (yy == rc3.Height() / 2) + { + targetY = y; + goto loopEnd; + } + } + else + { + break; + } + } + } + } + else + { + break; + } + } + } + + loopEnd: + if (targetY > 0) + { + ::BitBlt(hImage1DC, (rc1.Width() - rc3.Width()) / 2, targetY, rc3.Width(), rc3.Height(), hImage3DC, 0, 0, SRCCOPY); + } + image3->ReleaseDC(); + } + } + else + { + ::PrintWindow(m_hWnd, hImage1DC, 0); + } + + pDwmGetWindowAttribute(m_hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, &rc2, sizeof(rc2)); + // cstr.Format(_T("Width=%d/%d, Height=%d/%d"), rc1.Width(), rc2.Width(), rc1.Height(), rc2.Height()); + // AfxMessageBox(cstr); + if (rc1.Width() > rc2.Width()) + { + if (image2->Create(rc2.Width(), rc2.Height(), 32)) + { + HDC hImage2DC = image2->GetDC(); + ::BitBlt(hImage2DC, 0, 0, rc2.Width(), rc2.Height(), hImage1DC, (rc1.Width() - rc2.Width()) / 2, 0, SRCCOPY); + image2->ReleaseDC(); + SaveImageDlg(image2); + } + else + { + SaveImageDlg(image1); + } + } + else + { + SaveImageDlg(image1); + } + image1->ReleaseDC(); + } + } + else + { + GetWindowRect(&rc1); + if (image1->Create(rc1.Width(), rc1.Height(), 32)) + { + HDC hImage1DC = image1->GetDC(); + ::PrintWindow(m_hWnd, hImage1DC, 0); + SaveImageDlg(image1); + image1->ReleaseDC(); + } + } + SAFE_DELETE(image1); + SAFE_DELETE(image2); + SAFE_DELETE(image3); +#endif +} + +void CMainDialogFx::SaveImageDlg(CImage* image) +{ + CString path; + SYSTEMTIME st; + GetLocalTime(&st); + path.Format(_T("%s_%04d%02d%02d%02d%02d%02d"), PRODUCT_FILENAME, st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); + + CString filter = _T("PNG (*.png)|*.png|BMP (*.bmp)|*.bmp||"); + CFileDialog save(FALSE, _T(""), path, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER, filter); + + if (save.DoModal() == IDOK) + { + image->Save(save.GetPathName().GetString()); + } +} + +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Task Tray +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef OPTION_TASK_TRAY + +UINT CMainDialogFx::wmTaskbarCreated = ::RegisterWindowMessage(_T("TaskbarCreated")); + +// Add TaskTray +BOOL CMainDialogFx::AddTaskTray(UINT id, UINT callback, HICON icon, CString tip) +{ + if(m_bResident) + { + NOTIFYICONDATA nidata = {0}; + nidata.cbSize = sizeof(NOTIFYICONDATA); + nidata.hWnd = m_hWnd; + nidata.uID = id; + nidata.uFlags = NIF_TIP | NIF_ICON | NIF_MESSAGE; + nidata.hIcon = icon; + nidata.uVersion = NOTIFYICON_VERSION; + nidata.uCallbackMessage = callback; + _tcscpy_s(nidata.szTip, 128, tip.Left(127)); + + ::Shell_NotifyIcon(NIM_SETVERSION, &nidata); + int waitCount = 10; + if(m_bStartup) + { + waitCount = 20; + } + for(int i = 0; i < waitCount; i++) + { + if(::Shell_NotifyIcon(NIM_ADD, &nidata)) + { + return TRUE; + } + Sleep(100 * i); + } + } + return FALSE; +} + +// Update TaskTray Icon +BOOL CMainDialogFx::ModifyTaskTrayIcon(UINT id, HICON icon) +{ + if(m_bResident) + { + NOTIFYICONDATA nidata = { 0 }; + nidata.cbSize = sizeof(NOTIFYICONDATA); + nidata.hWnd = m_hWnd; + nidata.uID = id; + nidata.uFlags = NIF_ICON; + nidata.hIcon = icon; + for(int i = 0; i < 3; i++) + { + if(::Shell_NotifyIcon(NIM_MODIFY, &nidata)) + { + return TRUE; + } + Sleep(100 * i); + } + } + return FALSE; +} + +// Update TaskTray Tips +BOOL CMainDialogFx::ModifyTaskTrayTip(UINT id, CString tip) +{ + if(m_bResident) + { + NOTIFYICONDATA nidata = { 0 }; + nidata.cbSize = sizeof(NOTIFYICONDATA); + nidata.hWnd = m_hWnd; + nidata.uID = id; + nidata.uFlags = NIF_TIP; + + _tcscpy_s(nidata.szTip, 128, tip.Left(127)); + + for(int i = 0; i < 3; i++) + { + if(::Shell_NotifyIcon(NIM_MODIFY, &nidata)) + { + return TRUE; + } + Sleep(100 * i); + } + } + return FALSE; +} + +// Update TaskTray +BOOL CMainDialogFx::ModifyTaskTray(UINT id, HICON icon, CString tip) +{ + if(m_bResident) + { + NOTIFYICONDATA nidata = { 0 }; + nidata.cbSize = sizeof(NOTIFYICONDATA); + nidata.hWnd = m_hWnd; + nidata.uID = id; + nidata.uFlags = NIF_TIP|NIF_ICON; + nidata.hIcon = icon; + _tcscpy_s(nidata.szTip, 128, tip.Left(127)); + + for(int i = 0; i < 3; i++) + { + if(::Shell_NotifyIcon(NIM_MODIFY, &nidata)) + { + return TRUE; + } + Sleep(100 * i); + } + } + return FALSE; +} + +// Show Balloon +BOOL CMainDialogFx::ShowBalloon(UINT id, DWORD infoFlag, CString infoTitle, CString info) +{ + if(m_bResident) + { + NOTIFYICONDATA nidata = { 0 }; + nidata.cbSize = sizeof(NOTIFYICONDATA); + nidata.hWnd = m_hWnd; + nidata.uID = id; + nidata.uFlags = NIF_INFO; + nidata.dwInfoFlags = infoFlag; + + _tcscpy_s(nidata.szInfo, 256, info.Left(255)); + _tcscpy_s(nidata.szInfoTitle, 64, infoTitle.Left(63)); + + for(int i = 0; i < 3; i++) + { + if(::Shell_NotifyIcon(NIM_MODIFY, &nidata)) + { + return TRUE; + } + Sleep(100 * i); + } + } + return FALSE; +} + +// Remove TaskTray +BOOL CMainDialogFx::RemoveTaskTray(UINT id) +{ + if(m_bResident) + { + NOTIFYICONDATA nidata = { 0 }; + nidata.cbSize = sizeof(NOTIFYICONDATA); + nidata.hWnd = m_hWnd; + nidata.uID = id; + + for(int i = 0; i < 3; i++) + { + if(::Shell_NotifyIcon(NIM_DELETE, &nidata)) + { + return TRUE; + } + Sleep(100 * i); + } + } + return FALSE; +} + +#endif \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/MainDialogFx.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/MainDialogFx.h new file mode 100644 index 0000000..012f8a3 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/MainDialogFx.h @@ -0,0 +1,85 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once +#include "DialogFx.h" + +class CMainDialogFx : public CDialogFx +{ +public: + CMainDialogFx(UINT dlgResouce, CWnd* pParent = NULL); + virtual ~CMainDialogFx(); + + // Zoom + DWORD GetZoomType(); + void SetZoomType(DWORD zoomType); + + // Getter + CString GetCurrentLangPath(); + CString GetDefaultLangPath(); + CString GetThemeDir(); + CString GetCurrentTheme(); + CString GetDefaultTheme(); + CString GetParentTheme1(); + CString GetParentTheme2(); + CString GetIniPath(); + + void SaveImage(); + +protected: + void InitMenu(); + void InitThemeLang(); + void ChangeTheme(CString themeName); + void SetWindowTitle(CString message); + void UpdateThemeInfo(); + COLORREF GetControlColor(CString name, BYTE defaultColor, CString theme); + COLORREF GetBackgroundColor(CString name, CString theme); + BYTE GetControlAlpha(CString name, BYTE defaultAlpha, CString theme); + BYTE GetCharacterPosition(CString theme); + CString GetParentTheme(int i, CString theme); + CString GetRandomTheme(); + void SaveImageDlg(CImage* image); + + virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam); + virtual BOOL OnInitDialog(); + virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); + virtual BOOL CheckThemeEdition(CString name); + virtual CString GetDefaultFont(); + + virtual void SaveWindowPosition(); + virtual void RestoreWindowPosition(); + + DECLARE_MESSAGE_MAP() + afx_msg void OnWindowPosChanging(WINDOWPOS* lpwndpos); + afx_msg void OnGetMinMaxInfo(MINMAXINFO* lpMMI); + + // Common + BOOL m_bStartup; + BOOL m_bWindowMinimizeOnce; + BOOL m_bResident; + BOOL m_bResidentMinimize; + + // Theme + CString m_ThemeKeyName; + CString m_RecommendTheme; + CStringArray m_MenuArrayTheme; + + // Language + CStringArray m_MenuArrayLang; + +#ifdef OPTION_TASK_TRAY + // Task Tray + static UINT wmTaskbarCreated; + BOOL AddTaskTray(UINT id, UINT callback, HICON icon, CString tip); + BOOL RemoveTaskTray(UINT id); + BOOL ModifyTaskTray(UINT id, HICON icon, CString tip); + BOOL ModifyTaskTrayIcon(UINT id, HICON icon); + BOOL ModifyTaskTrayTip(UINT id, CString tip); + BOOL ShowBalloon(UINT id, DWORD infoFlag, CString infoTitle, CString info); +#endif + +}; \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/OsInfoFx.cpp b/CristalDiskMark/source/CrystalDiskMark/Priscilla/OsInfoFx.cpp new file mode 100644 index 0000000..b409068 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/OsInfoFx.cpp @@ -0,0 +1,1558 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "OsInfoFx.h" +#include "UtilityFx.h" + +typedef BOOL (WINAPI* FuncGetProductInfo)(DWORD, DWORD, DWORD, DWORD, PDWORD); +typedef BOOL (WINAPI* FuncGetNativeSystemInfo)(LPSYSTEM_INFO); +typedef BOOL (WINAPI* FuncIsWow64Process)(HANDLE hProcess,PBOOL Wow64Process); +typedef LONG (WINAPI* FuncRtlGetVersion)(POSVERSIONINFOEXW osvi); + +#if _MSC_VER > 1310 +BOOL IsWindowsVersionOrGreaterFx(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor) +{ + OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 }; + DWORDLONG const dwlConditionMask = VerSetConditionMask( + VerSetConditionMask( + VerSetConditionMask( + 0, VER_MAJORVERSION, VER_GREATER_EQUAL), + VER_MINORVERSION, VER_GREATER_EQUAL), + VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + + osvi.dwMajorVersion = wMajorVersion; + osvi.dwMinorVersion = wMinorVersion; + osvi.wServicePackMajor = wServicePackMajor; + + return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE; +} + +BOOL IsWindowsBuildOrGreater(DWORD build) +{ + OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 }; + osvi.dwBuildNumber = build; + return (VerifyVersionInfoW(&osvi, VER_BUILDNUMBER, VerSetConditionMask(0, VER_BUILDNUMBER, VER_GREATER_EQUAL)) == TRUE); +} +#endif + +BOOL GetVersionFx(POSVERSIONINFOEXW osvi) +{ + static FuncRtlGetVersion pRtlGetVersion = NULL; + if (pRtlGetVersion == NULL) + { + HMODULE hModule = GetModuleHandle(_T("ntdll.dll")); + if (hModule) + { + pRtlGetVersion = (FuncRtlGetVersion)GetProcAddress(hModule, "RtlGetVersion"); + } + } + + if (pRtlGetVersion) + { + if (pRtlGetVersion(osvi) >= 0) // NT_SUCCESS(pRtlGetVersion(osvi) + { + return TRUE; + } + } + return FALSE; +} + +#if _MSC_VER > 1310 +BOOL IsX64() +{ + static BOOL b = -1; + if (b == -1) + { + b = FALSE; + HMODULE hModule = GetModuleHandle(_T("kernel32.dll")); + if (hModule) + { + FuncGetNativeSystemInfo pGetNativeSystemInfo = (FuncGetNativeSystemInfo)GetProcAddress(hModule, "GetNativeSystemInfo"); + if (pGetNativeSystemInfo != NULL) + { + SYSTEM_INFO si = { 0 }; + pGetNativeSystemInfo(&si); + if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) + { + b = TRUE; + } + } + } + } + return b; +} + +BOOL IsIa64() +{ + static BOOL b = -1; + if (b == -1) + { + b = FALSE; + HMODULE hModule = GetModuleHandle(_T("kernel32.dll")); + if (hModule) + { + FuncGetNativeSystemInfo pGetNativeSystemInfo = (FuncGetNativeSystemInfo)GetProcAddress(hModule, "GetNativeSystemInfo"); + if (pGetNativeSystemInfo != NULL) + { + SYSTEM_INFO si = { 0 }; + pGetNativeSystemInfo(&si); + if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64) + { + b = TRUE; + } + } + } + } + return b; +} + +BOOL IsArm32() +{ + static BOOL b = -1; + if (b == -1) + { + b = FALSE; + HMODULE hModule = GetModuleHandle(_T("kernel32.dll")); + if (hModule) + { + FuncGetNativeSystemInfo pGetNativeSystemInfo = (FuncGetNativeSystemInfo)GetProcAddress(hModule, "GetNativeSystemInfo"); + if (pGetNativeSystemInfo != NULL) + { + SYSTEM_INFO si = { 0 }; + pGetNativeSystemInfo(&si); + if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM) + { + b = TRUE; + } + } + } + } + return b; +} + +BOOL IsArm64() +{ + static BOOL b = -1; + if (b == -1) + { + b = FALSE; + HMODULE hModule = GetModuleHandle(_T("kernel32.dll")); + if (hModule) + { + FuncGetNativeSystemInfo pGetNativeSystemInfo = (FuncGetNativeSystemInfo)GetProcAddress(hModule, "GetNativeSystemInfo"); + if (pGetNativeSystemInfo != NULL) + { + SYSTEM_INFO si = { 0 }; + pGetNativeSystemInfo(&si); + if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64) + { + b = TRUE; + } + } + } + } + return b; +} + +BOOL IsWow64() +{ + static BOOL b = -1; + if (b == -1) + { + b = FALSE; + HMODULE hModule = GetModuleHandle(_T("kernel32.dll")); + if (hModule) + { + FuncIsWow64Process pIsWow64Process = (FuncIsWow64Process)GetProcAddress(hModule, "IsWow64Process"); + if (pIsWow64Process != NULL) + { + pIsWow64Process(GetCurrentProcess(), &b); + } + } + } + return b; +} + +BOOL IsIe556() +{ + switch (GetIeVersion()) + { + case 550: + case 600: + return TRUE; + break; + default: + return FALSE; + break; + } +} + +BOOL IsDotNet2() +{ + static BOOL b = -1; + + if (b == -1) + { + b = FALSE; + DWORD type = REG_DWORD; + ULONG size = sizeof(DWORD); + HKEY hKey = NULL; + DWORD buf = 0; + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v2.0.50727"), 0, KEY_READ, &hKey) == ERROR_SUCCESS + || RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Wow6432Node\\Microsoft\\NET Framework Setup\\NDP\\v2.0.50727"), 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + if (RegQueryValueEx(hKey, _T("Install"), NULL, &type, (LPBYTE)&buf, &size) == ERROR_SUCCESS) + { + if (buf == 1) + { + b = TRUE; + } + } + RegCloseKey(hKey); + } + } + + return (BOOL)b; +} + +BOOL IsDotNet4() +{ + static BOOL b = -1; + + if (b == -1) + { + b = FALSE; + DWORD type = REG_DWORD; + ULONG size = sizeof(DWORD); + HKEY hKey = NULL; + DWORD buf = 0; + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Client"), 0, KEY_READ, &hKey) == ERROR_SUCCESS + || RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full"), 0, KEY_READ, &hKey) == ERROR_SUCCESS + || RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\WOW6432Node\\Microsoft\\NET Framework Setup\\NDP\\v4\\Client"), 0, KEY_READ, &hKey) == ERROR_SUCCESS + || RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\WOW6432Node\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full"), 0, KEY_READ, &hKey) == ERROR_SUCCESS + ) + { + if (RegQueryValueEx(hKey, _T("Install"), NULL, &type, (LPBYTE)&buf, &size) == ERROR_SUCCESS) + { + if (buf == 1) + { + b = TRUE; + } + } + RegCloseKey(hKey); + } + } + + return b; +} + +BOOL IsDotNet48() +{ + static BOOL b = -1; + + if (b == -1) + { + b = FALSE; + DWORD type = REG_DWORD; + ULONG size = sizeof(DWORD); + HKEY hKey = NULL; + DWORD buf = 0; + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Client"), 0, KEY_READ, &hKey) == ERROR_SUCCESS + || RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full"), 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + if (RegQueryValueEx(hKey, _T("Install"), NULL, &type, (LPBYTE)&buf, &size) == ERROR_SUCCESS) + { + if (buf == 1) + { + if (RegQueryValueEx(hKey, _T("Release"), NULL, &type, (LPBYTE)&buf, &size) == ERROR_SUCCESS) + { + if (buf >= 528040) + { + b = TRUE; + } + } + } + } + RegCloseKey(hKey); + } + } + + return b; +} + +BOOL IsNT5() +{ + static BOOL b = -1; + if (b == -1) + { + b = !IsWindowsVersionOrGreaterFx(6, 0) && IsWindowsVersionOrGreaterFx(5, 0) ? TRUE : FALSE; + } + return b; +} + +BOOL IsNT6orLater() +{ + static BOOL b = -1; + if (b == -1) + { + b = IsWindowsVersionOrGreaterFx(6, 0) ? TRUE : FALSE; + } + return b; +} + +BOOL IsWin2k() +{ + static BOOL b = -1; + if (b == -1) + { + b = IsWindowsVersionOrGreaterFx(5, 0) && !IsWindowsVersionOrGreaterFx(5, 1) ? TRUE : FALSE; + } + return b; +} + +BOOL IsWinXpOrLater() +{ + static BOOL b = -1; + if (b == -1) + { + b = IsWindowsVersionOrGreaterFx(5, 1) ? TRUE : FALSE; + } + return b; +} + +BOOL IsWinXpLuna() +{ + static BOOL xp = -1; + BOOL b = FALSE; + if (xp == -1) + { + xp = IsWindowsVersionOrGreaterFx(5, 1) && !IsWindowsVersionOrGreaterFx(6, 0) ? TRUE : FALSE; + } + if (xp == FALSE) + { + return FALSE; + } + + // Luna Check + DWORD type = REG_DWORD; + ULONG size = 256; + HKEY hKey = NULL; + BYTE buf[256] = {0}; + CString cstr; + + if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\Microsoft\\Windows\\CurrentVersion\\ThemeManager\\"), 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + if (RegQueryValueEx(hKey, _T("ThemeActive"), NULL, &type, buf, &size) == ERROR_SUCCESS) + { + cstr = (TCHAR*)buf; + if (cstr.Find(_T("1")) == 0) + { + b = TRUE; + } + } + RegCloseKey(hKey); + } + return b; +} + +BOOL IsWin8orLater() +{ + static BOOL b = -1; + if (b == -1) + { + b = IsWindowsVersionOrGreaterFx(6, 2) ? TRUE : FALSE; + } + return b; +} + +BOOL IsWin81orLater() +{ + static BOOL b = -1; + if (b == -1) + { + b = IsWindowsVersionOrGreaterFx(6, 3) ? TRUE : FALSE; + } + return b; +} + +BOOL IsDarkModeSupport() +{ + static BOOL b = -1; + if (b == -1) + { + b = IsWindowsBuildOrGreater(17763) ? TRUE : FALSE; + } + return b; +} + +BOOL HasSidebar() +{ + static BOOL b = -1; + if (b == -1) + { + b = FALSE; + OSVERSIONINFOEXW osvi; + GetVersionFx(&osvi); + + if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion < 2 && osvi.wProductType == VER_NT_WORKSTATION) + { + b = TRUE; + } + } + return b; +} + +DWORD GetIeVersion() +{ + static INT ieVersion = -1; + + if (ieVersion != -1) + { + return ieVersion; + } + + DWORD type = REG_SZ; + ULONG size = 256; + HKEY hKey = NULL; + BYTE buf[256] = {0}; + CString cstr; + + switch (GetFileVersion(_T("Shdocvw.dll"))) + { + case 470: ieVersion = 300; break; + case 471: ieVersion = 400; break; + case 472: ieVersion = 401; break; + case 500: ieVersion = 500; break; + case 550: ieVersion = 550; break; + case 600: + default: + ieVersion = 600; + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Internet Explorer"), 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + if (RegQueryValueEx(hKey, _T("Version"), NULL, &type, buf, &size) == ERROR_SUCCESS) + { + cstr = (TCHAR*)buf; + ieVersion = _tstoi(cstr) * 100; + if (ieVersion == 900 && RegQueryValueEx(hKey, _T("svcVersion"), NULL, &type, buf, &size) == ERROR_SUCCESS) + { + cstr = (TCHAR*)buf; + if (_tstoi(cstr) * 100 > 900) + { + ieVersion = _tstoi(cstr) * 100; + } + } + } + RegCloseKey(hKey); + } + break; + } + + return ieVersion; +} +#endif + +BOOL IsNT3() +{ + static BOOL b = -1; + if (b == -1) + { + b = FALSE; + + OSVERSIONINFO osvi = { 0 }; + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx((OSVERSIONINFO*)&osvi); + + if ((osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) + && (osvi.dwMajorVersion == 3)) + { + b = TRUE; + } + } + return b; +} + +BOOL IsNT4() +{ + static BOOL b = -1; + if (b == -1) + { + b = FALSE; + + OSVERSIONINFO osvi = { 0 }; + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx((OSVERSIONINFO*)&osvi); + + if ((osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) + && (osvi.dwMajorVersion == 4)) + { + b = TRUE; + } + } + return b; +} + +BOOL IsWin9x() +{ + static BOOL b = -1; + + if (b == -1) + { + b = FALSE; + + OSVERSIONINFO osvi = { 0 }; + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx((OSVERSIONINFO*)&osvi); + + if (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) + { + b = TRUE; + } + } + + return b; +} + +BOOL IsWin95() +{ + static BOOL b = -1; + + if (b == -1) + { + b = FALSE; + + OSVERSIONINFO osvi = { 0 }; + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx((OSVERSIONINFO*)&osvi); + + if (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS + && osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 0) + { + b = TRUE; + } + } + + return b; +} + +BOOL IsWin95First() +{ + static BOOL b = -1; + + if (b == -1) + { + b = FALSE; + + OSVERSIONINFO osvi = { 0 }; + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx((OSVERSIONINFO*)&osvi); + + if (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS + && osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber <= 950) + { + b = TRUE; + } + } + + return b; +} + +BOOL IsPC98() +{ + static BOOL b = -1; + + DWORD type = REG_SZ; + ULONG size = 256; + HKEY hKey = NULL; + BYTE buf[256] = {0}; + CString cstr; + + if (b == -1) + { + b = FALSE; + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DESCRIPTION\\System"), 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + if (RegQueryValueEx(hKey, _T("Identifier"), NULL, &type, buf, &size) == ERROR_SUCCESS) + { + cstr = (TCHAR*)buf; + if (cstr.Find(_T("NEC PC-98")) == 0) + { + b = TRUE; + } + } + RegCloseKey(hKey); + } + + if ((GetKeyboardType(1) & 0xFF00) == 0x0D00) + { + b = TRUE; + } + +#ifndef UNICODE +/// PC-98 機種判定 det98 Programmed by K.Takata +/// http://k-takata.o.oo7.jp/mysoft/det98.html +#define MK_FP(seg, off) ((void *) (((seg) << 4) + (off))) +#define MACHINE_ID_SEG 0xf8e8 +#define MACHINE_ID_SEG_HIGHRESO 0xffe8 +#define PC98_ID_OFF 0x0000 +#define PC98_ID 0x2198 /* 9821 */ + + if(IsWin9x()) + { + unsigned short* pc98_id = NULL; + pc98_id = (unsigned short*)MK_FP(MACHINE_ID_SEG, PC98_ID_OFF); + if(*pc98_id == PC98_ID) + { + b = TRUE; + } + + if((GetSystemMetrics(SM_CXSCREEN) == 1120) && (GetSystemMetrics(SM_CYSCREEN) == 750)) // High Resolution + { + pc98_id = (unsigned short*)MK_FP(MACHINE_ID_SEG_HIGHRESO, PC98_ID_OFF); + if (*pc98_id == PC98_ID) + { + b = TRUE; + } + } + } +#endif + } + + return b; +} + +BOOL IsNT51orlater() +{ + static BOOL b = -1; + if (b == -1) + { + b = FALSE; + + OSVERSIONINFO osvi = { 0 }; + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx((OSVERSIONINFO*)&osvi); + + if ((osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) + && + ((osvi.dwMajorVersion == 5 && osvi.dwMinorVersion >= 1) || (osvi.dwMajorVersion > 5))) + { + b = TRUE; + } + } + return b; +} + +BOOL IsRunningOnWine() +{ + HMODULE hModule = LoadLibraryA("ntdll.dll"); + if (hModule) + { + void* pWineGetVersion = (void*)GetProcAddress(hModule, "wine_get_version"); + FreeLibrary(hModule); + return pWineGetVersion != NULL; + } + return FALSE; +} + +void GetOsName(CString& osFullName, CString& name, CString& version, CString& architecture) +{ + CString osName, osType, osCsd, osVersion, osBuild, osArchitecture, osDisplayVersion; + CString osNameWmi; + CString cstr; + +#if _MSC_VER <= 1310 + // For Win9x + OSVERSIONINFO osv = { 0 }; + osv.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx((OSVERSIONINFO*)&osv); + + if (osv.dwPlatformId == VER_PLATFORM_WIN32s) + { + osFullName = _T("Windows 3.x + Win32s"); + name = osFullName; + return; + } + else if (osv.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) + { + switch (osv.dwMinorVersion) + { + case 0: // Windows 95 + if (LOWORD(osv.dwBuildNumber) >= 1214) + { + osName = _T("Windows 95 OSR2.5"); + } + else if (LOWORD(osv.dwBuildNumber) >= 1212) + { + osName = _T("Windows 95 OSR2.1"); + } + else if (LOWORD(osv.dwBuildNumber) >= 1111) + { + osName = _T("Windows 95 OSR2"); + } + else + { + osName = _T("Windows 95"); + } + break; + case 10: // Windows 98 + if (LOWORD(osv.dwBuildNumber) >= 2222) + { + osName = _T("Windows 98 Second Edition"); + } + else if (LOWORD(osv.dwBuildNumber) >= 2000) + { + osName = _T("Windows 98 SP1"); + } + else + { + osName = _T("Windows 98"); + } + break; + case 90: + osName = _T("Windows Me"); + break; + default: + osName = _T("Windows 9x"); + break; + } + osVersion.Format(_T("%d.%d"), osv.dwMajorVersion, osv.dwMinorVersion); + osBuild.Format(_T("%d"), LOWORD(osv.dwBuildNumber)); + osFullName.Format(_T("%s [%s Build %s]"), (LPCTSTR)osName, (LPCTSTR)osVersion, (LPCTSTR)osBuild); + + name = osName; + version.Format(_T("%s Build %s"), (LPCTSTR)osVersion, (LPCTSTR)osBuild); + architecture = _T("x86"); + + return; + } +#endif + +#if _MSC_VER > 1310 + #ifdef UNICODE + OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 }; + GetVersionFx((OSVERSIONINFOEXW*)&osvi); + #else + OSVERSIONINFOEX osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 }; + GetVersionEx((OSVERSIONINFO*)&osvi); + #endif +#else + OSVERSIONINFOEXW osviw = { sizeof(osviw), 0, 0, 0, 0, {0}, 0, 0 }; + OSVERSIONINFOEX osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 }; + + GetVersionEx((OSVERSIONINFO*)&osvi); + if (osvi.dwMajorVersion == 0) // Windows NT 3.51, NT 4.0 SP4 or earlier + { + osvi.dwMajorVersion = osv.dwMajorVersion; + osvi.dwMinorVersion = osv.dwMinorVersion; + osvi.dwBuildNumber = osv.dwBuildNumber; + osvi.dwPlatformId = osv.dwPlatformId; + wsprintf(osvi.szCSDVersion, osv.szCSDVersion); + } + + if (osvi.dwMajorVersion >= 6 && GetVersionFx((OSVERSIONINFOEXW*)&osviw)) + { + osvi.dwMajorVersion = osviw.dwMajorVersion; + osvi.dwMinorVersion = osviw.dwMinorVersion; + osvi.dwBuildNumber = osviw.dwBuildNumber; + osvi.wServicePackMajor = osviw.wServicePackMajor; + osvi.wServicePackMinor = osviw.wServicePackMinor; + osvi.wSuiteMask = osviw.wSuiteMask; + osvi.wProductType = osviw.wProductType; + } +#endif + + GetOsNameWmi(osNameWmi); + + // Windows 11/Server 2025 + CString osNameWmiBackup = osNameWmi; + CString osNameWmiMajorVersion = osNameWmi; + osNameWmiMajorVersion.Replace(_T("(R)"), _T("")); + osNameWmiMajorVersion.Replace(_T("Microsoft "), _T("")); + osNameWmiMajorVersion.Replace(_T("Windows "), _T("")); + osNameWmiMajorVersion.Replace(_T("Server "), _T("")); + + int majorVersion = _ttoi(osNameWmiMajorVersion); + + if ((3 <= majorVersion && majorVersion <= 11) || (2000 <= majorVersion && majorVersion <= 2025)) + { + osNameWmi = _T(""); + } + + if (osNameWmi.IsEmpty()) + { + switch (osvi.dwPlatformId) + { + case VER_PLATFORM_WIN32_NT: + if (osvi.dwMajorVersion == 3) + { + osName.Format(_T("Windows NT 3.%d"), osvi.dwMinorVersion); + } + else if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 0) + { + osName = _T("Windows NT 4.0"); + } + else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0) + { + osName = _T("Windows 2000"); + } + else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1) + { + osName = _T("Windows XP"); + } + else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2) + { + if (GetSystemMetrics(SM_SERVERR2)) + { + osName = _T("Windows Server 2003 R2"); + } + else if (osvi.wSuiteMask == 0x00002000 /*VER_SUITE_STORAGE_SERVER*/) + { + osName = _T("Windows Storage Server 2003"); + } + else if (osvi.wProductType == VER_NT_WORKSTATION) + { + osName = _T("Windows XP"); + } + else + { + osName = _T("Windows Server 2003"); + } + } + else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0) + { + if (osvi.wProductType != VER_NT_WORKSTATION) + { + osName = _T("Windows Server 2008"); + } + else + { + osName = _T("Windows Vista"); + } + } + else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1) + { + if (osvi.wProductType != VER_NT_WORKSTATION) + { + osName = _T("Windows Server 2008 R2"); + } + else + { + osName = _T("Windows 7"); + } + } + else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 2) + { + if (osvi.wProductType != VER_NT_WORKSTATION) + { + osName = _T("Windows Server 2012"); + } + else + { + osName = _T("Windows 8"); + } + } + else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 3) + { + if (osvi.wProductType != VER_NT_WORKSTATION) + { + osName = _T("Windows Server 2012 R2"); + } + else + { + osName = _T("Windows 8.1"); + } + } + else if ((osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 4) + || (osvi.dwMajorVersion >= 10 && osvi.dwMinorVersion >= 0)) + { + if (osvi.wProductType != VER_NT_WORKSTATION) + { + if (osvi.dwBuildNumber >= 26040) + { + osName = _T("Windows Server 2025"); + } + else if (osvi.dwBuildNumber >= 20344) + { + osName = _T("Windows Server 2022"); + } + else if (osvi.dwBuildNumber >= 17763) + { + osName = _T("Windows Server 2019"); + } + else + { + osName = _T("Windows Server 2016"); + } + } + else + { + if (osvi.dwBuildNumber >= 21996) + { + osName = _T("Windows 11"); + } + else + { + osName = _T("Windows 10"); + } + } + } + else + { + osName.Format(_T("Windows NT %d.%d"), osvi.dwMajorVersion, osvi.dwMinorVersion); + } + + if (osvi.dwMajorVersion >= 6) + { + HMODULE hModule = GetModuleHandle(_T("kernel32.dll")); + FuncGetProductInfo pGetProductInfo = NULL; + if (hModule) + { + pGetProductInfo = (FuncGetProductInfo)GetProcAddress(hModule, "GetProductInfo"); + } + + if (pGetProductInfo) + { + DWORD productType = 0; + pGetProductInfo(osvi.dwMajorVersion, osvi.dwMinorVersion, 0, 0, &productType); + + switch (productType) + { + case PRODUCT_UNLICENSED: + osType = _T("Unlicensed"); + break; + case PRODUCT_CORE_ARM: + case PRODUCT_CORE_COUNTRYSPECIFIC: + case PRODUCT_CORE_SINGLELANGUAGE: + case PRODUCT_CORE: + osType = L"Home"; + break; + case PRODUCT_CORE_N: + osType = L"Home N"; + break; + case PRODUCT_EDUCATION: + osType = L"Education"; + break; + case PRODUCT_EDUCATION_N: + osType = L"Education N"; + break; + case PRODUCT_BUSINESS: + osType = _T("Business"); + break; + case PRODUCT_BUSINESS_N: + osType = _T("Business N"); + break; + case PRODUCT_CLUSTER_SERVER: + osType = _T("Cluster Server"); + break; + case PRODUCT_DATACENTER_SERVER: + osType = _T("Datacenter"); + break; + case PRODUCT_DATACENTER_SERVER_CORE: + osType = _T("Datacenter"); + break; + case PRODUCT_ENTERPRISE: + osType = _T("Enterprise"); + break; + case PRODUCT_ENTERPRISE_N: + osType = _T("Enterprise N"); + break; + case PRODUCT_ENTERPRISE_G: + osType = _T("Enterprise G"); + break; + case PRODUCT_ENTERPRISE_SERVER: + osType = _T("Enterprise"); + break; + case PRODUCT_ENTERPRISE_SERVER_CORE: + osType = _T("Enterprise"); + break; + case PRODUCT_ENTERPRISE_SERVER_IA64: + osType = _T("Datacenter Enterprise for Itanium-based Systems"); + break; + case PRODUCT_HOME_BASIC: + osType = _T("Home Basic"); + break; + case PRODUCT_HOME_BASIC_N: + osType = _T("Home Basic N"); + break; + case PRODUCT_HOME_PREMIUM: + osType = _T("Home Premium"); + break; + case PRODUCT_HOME_PREMIUM_N: + osType = _T("Home Premium N"); + break; + case PRODUCT_HOME_SERVER: + osType = _T("Home Server"); + break; + case PRODUCT_SERVER_FOR_SMALLBUSINESS: + osType = _T("Server for Small Business"); + break; + case PRODUCT_SMALLBUSINESS_SERVER: + osType = _T("Small Business Server"); + break; + case PRODUCT_SMALLBUSINESS_SERVER_PREMIUM: + osType = _T("Small Business Server Premium"); + break; + case PRODUCT_STANDARD_SERVER: + osType = _T("Server Standard"); + break; + case PRODUCT_STANDARD_SERVER_CORE: + osType = _T("Server Standard"); + break; + case PRODUCT_STARTER: + osType = _T("Starter"); + break; + case PRODUCT_STARTER_N: + osType = _T("Starter N"); + break; + case PRODUCT_STARTER_E: + osType = _T("Starter E"); + break; + case PRODUCT_STORAGE_ENTERPRISE_SERVER: + osType = _T("Storage Server Enterprise"); + break; + case PRODUCT_STORAGE_EXPRESS_SERVER: + osType = _T("Storage Server Express"); + break; + case PRODUCT_STORAGE_STANDARD_SERVER: + osType = _T("Storage Server Standard"); + break; + case PRODUCT_STORAGE_WORKGROUP_SERVER: + osType = _T("Storage Server Workgroup"); + break; + case PRODUCT_ULTIMATE: + osType = _T("Ultimate"); + break; + case PRODUCT_ULTIMATE_N: + osType = _T("Ultimate N"); + break; + case PRODUCT_WEB_SERVER: + osType = _T("Web Server"); + break; + case PRODUCT_PROFESSIONAL: + if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion <= 2) + { + osType = _T("Professional"); + } + else + { + osType = _T("Pro"); + } + break; + case PRODUCT_PROFESSIONAL_N: + if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion <= 2) + { + osType = _T("Professional N"); + } + else + { + osType = _T("Pro N"); + } + break; + case PRODUCT_PRO_WORKSTATION: + case PRODUCT_PROFESSIONAL_WORKSTATION: + osType = _T("Pro for Workstation"); + break; + case PRODUCT_PRO_WORKSTATION_N: + case PRODUCT_PROFESSIONAL_WORKSTATION_N: + osType = _T("Pro for Workstation N"); + break; + case PRODUCT_PRO_FOR_EDUCATION: + osType = _T("Pro for Education"); + break; + case PRODUCT_PRO_FOR_EDUCATION_N: + osType = _T("Pro for Education N"); + break; + case PRODUCT_ESSENTIALBUSINESS_SERVER_ADDL: + case PRODUCT_ESSENTIALBUSINESS_SERVER_MGMTSVC: + case PRODUCT_ESSENTIALBUSINESS_SERVER_ADDLSVC: + osType = _T("Essentials Server"); + break; + } + } + } + else if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 0) + { + if (osvi.wProductType == VER_NT_WORKSTATION) + { + osType = _T("Workstation"); + } + else if (osvi.wProductType == VER_NT_SERVER || osvi.wProductType == VER_NT_DOMAIN_CONTROLLER) + { + if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE) + { + osType = _T("Server Enterprise Edition"); + } + else + { + osType = _T("Server"); + } + } + } + else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0) + { + if (osvi.wProductType == VER_NT_WORKSTATION) + { + osType = _T("Professional"); + } + else if (osvi.wProductType == VER_NT_SERVER || osvi.wProductType == VER_NT_DOMAIN_CONTROLLER) + { + if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE) + { + osType = _T("Advanced Server"); + } + else if (osvi.wSuiteMask & VER_SUITE_DATACENTER) + { + osType = _T("Datacenter Server"); + } + else + { + osType = _T("Server"); + } + } + } + else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion >= 1) + { + if (osvi.wSuiteMask & VER_SUITE_PERSONAL) + { + osType = _T("Home Edition"); + } + else if (osvi.wSuiteMask & VER_SUITE_DATACENTER) + { + osType = _T("Datacenter Edition"); + } + else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE) + { + osType = _T("Enterprise Edition"); + } + else if (osvi.wSuiteMask & VER_SUITE_BLADE) + { + osType = _T("Web Edition"); + } + else if (osvi.wProductType == VER_NT_WORKSTATION) + { + osType = _T("Professional"); + } + + // Meida Center & Tablet + if (GetSystemMetrics(SM_MEDIACENTER)) + { + TCHAR path[MAX_PATH] = {0}; + TCHAR str[256] = {0}; + UINT length = GetWindowsDirectory(path, MAX_PATH); + +#if _MSC_VER <= 1310 + _tcscat(path, _T("\\ehome\\ehshell.exe")); +#else + _tcscat_s(path, MAX_PATH, _T("\\ehome\\ehshell.exe")); +#endif + + if (length != 0 && GetFileVersion(path, str)) + { + cstr = str; + if (cstr.Find(_T("5.1")) == 0) + { + osType = _T("Media Center "); + cstr.Replace(_T("5.1."), _T("")); + double num = _tstof(cstr); + if (num <= 2600.1200) + { + osType += _T("2002"); + } + else if (num <= 2600.2500) + { + osType += _T("2004"); + } + else + { + osType += _T("2005"); + } + } + } + } + else if (GetSystemMetrics(SM_TABLETPC)) + { + osType = _T("Tablet PC"); + } + } + } + } + + osCsd = osvi.szCSDVersion; + osCsd.Replace(_T("Service Pack "), _T("SP")); + + if (osvi.dwMajorVersion >= 10) + { + DWORD value = 0; + DWORD type = REG_SZ; + TCHAR str[256]; + ULONG size = 256 * sizeof(TCHAR); + HKEY hKey = NULL; + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + if (RegQueryValueEx(hKey, _T("DisplayVersion"), NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS) + { + osDisplayVersion = str; + } + + if (osDisplayVersion.IsEmpty() && RegQueryValueEx(hKey, _T("ReleaseId"), NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS) + { + osDisplayVersion = str; + } + } + } + + osVersion.Format(_T("%d.%d"), osvi.dwMajorVersion, osvi.dwMinorVersion); + osBuild.Format(_T("%d"), osvi.dwBuildNumber); + +#if _MSC_VER > 1310 + if(IsX64()) + { + osArchitecture = _T("x64"); + } + else if (IsArm32()) + { + osArchitecture = _T("ARM32"); + } + else if (IsArm64()) + { + osArchitecture = _T("ARM64"); + } + else if(IsIa64()) + { + osArchitecture = _T("IA64"); + } + else + { + osArchitecture = _T("x86"); + } +#else + osArchitecture = _T("x86"); +#endif + + // for Unknown Edition + if (osType.IsEmpty()) + { + osNameWmi = osNameWmiBackup; + } + + if (!osNameWmi.IsEmpty()) + { + if (!osDisplayVersion.IsEmpty()) + { + osFullName.Format(_T("%s %s [%s Build %s] (%s)"), (LPCTSTR)osNameWmi, (LPCTSTR)osDisplayVersion, (LPCTSTR)osVersion, (LPCTSTR)osBuild, (LPCTSTR)osArchitecture); + name.Format(_T("%s %s"), (LPCTSTR)osNameWmi, (LPCTSTR)osDisplayVersion); + version.Format(_T("%s Build %s"), (LPCTSTR)osVersion, (LPCTSTR)osBuild); + architecture = osArchitecture; + } + else if (!osCsd.IsEmpty()) + { + osFullName.Format(_T("%s %s [%s Build %s] (%s)"), (LPCTSTR)osNameWmi, (LPCTSTR)osCsd, (LPCTSTR)osVersion, (LPCTSTR)osBuild, (LPCTSTR)osArchitecture); + name.Format(_T("%s %s"), (LPCTSTR)osNameWmi, (LPCTSTR)osCsd); + version.Format(_T("%s Build %s"), (LPCTSTR)osVersion, (LPCTSTR)osBuild); + architecture = osArchitecture; + } + else + { + osFullName.Format(_T("%s [%s Build %s] (%s)"), (LPCTSTR)osNameWmi, (LPCTSTR)osVersion, (LPCTSTR)osBuild, (LPCTSTR)osArchitecture); + name.Format(_T("%s"), (LPCTSTR)osNameWmi); + version.Format(_T("%s Build %s"), (LPCTSTR)osVersion, (LPCTSTR)osBuild); + architecture = osArchitecture; + } + } + else + { + if (!osDisplayVersion.IsEmpty()) + { + osFullName.Format(_T("%s %s %s [%s Build %s] (%s)"), (LPCTSTR)osName, (LPCTSTR)osType, (LPCTSTR)osDisplayVersion, (LPCTSTR)osVersion, (LPCTSTR)osBuild, (LPCTSTR)osArchitecture); + name.Format(_T("%s %s %s"), (LPCTSTR)osName, (LPCTSTR)osType, (LPCTSTR)osDisplayVersion); + version.Format(_T("%s Build %s"), (LPCTSTR)osVersion, (LPCTSTR)osBuild); + architecture = osArchitecture; + } + else if (!osCsd.IsEmpty()) + { + osFullName.Format(_T("%s %s %s [%s Build %s] (%s)"), (LPCTSTR)osName, (LPCTSTR)osType, (LPCTSTR)osCsd, (LPCTSTR)osVersion, (LPCTSTR)osBuild, (LPCTSTR)osArchitecture); + name.Format(_T("%s %s %s"), (LPCTSTR)osName, (LPCTSTR)osType, (LPCTSTR)osCsd); + version.Format(_T("%s Build %s"), (LPCTSTR)osVersion, (LPCTSTR)osBuild); + architecture = osArchitecture; + } + else if (!osType.IsEmpty()) + { + osFullName.Format(_T("%s %s [%s Build %s] (%s)"), (LPCTSTR)osName, (LPCTSTR)osType, (LPCTSTR)osVersion, (LPCTSTR)osBuild, (LPCTSTR)osArchitecture); + name.Format(_T("%s %s"), (LPCTSTR)osName, (LPCTSTR)osType); + version.Format(_T("%s Build %s"), (LPCTSTR)osVersion, (LPCTSTR)osBuild); + architecture = osArchitecture; + } + else + { + osFullName.Format(_T("%s [%s Build %s] (%s)"), (LPCTSTR)osName, (LPCTSTR)osVersion, (LPCTSTR)osBuild, (LPCTSTR)osArchitecture); + name.Format(_T("%s"), (LPCTSTR)osName); + version.Format(_T("%s Build %s"), (LPCTSTR)osVersion, (LPCTSTR)osBuild); + architecture = osArchitecture; + } + } + + osFullName.Replace(_T(" "), _T(" ")); +} + +//------------------------------------------------ +// Get OS Information by WMI +//------------------------------------------------ + +//warning : enum3, enum class +#if _MSC_VER > 1310 +#pragma warning(disable : 26812) +#endif + +#include +#include +#include +#pragma comment(lib, "oleaut32.lib") +#pragma comment(lib, "wbemuuid.lib") + +#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } +#ifndef safeCloseHandle +#define safeCloseHandle(h) { if( h != NULL ) { ::CloseHandle(h); h = NULL; } } +#endif +#ifndef safeVirtualFree +#define safeVirtualFree(h,b,c) { if( h != NULL ) { ::VirtualFree(h, b, c); h = NULL; } } +#endif + +void GetOsNameWmi(CString& osName) +{ + CString query = _T("Select * from Win32_OperatingSystem"); + + IWbemLocator* pIWbemLocator = NULL; + IWbemServices* pIWbemServices = NULL; + IEnumWbemClassObject* pEnumCOMDevs = NULL; + IWbemClassObject* pCOMDev = NULL; + ULONG uReturned = 0; + BOOL flag = FALSE; + + try + { + if (SUCCEEDED(CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, + IID_IWbemLocator, (LPVOID*)&pIWbemLocator))) + { + long securityFlag = 0; +#if _MSC_VER > 1310 + if (IsWindowsVersionOrGreaterFx(6, 0)) { securityFlag = WBEM_FLAG_CONNECT_USE_MAX_WAIT; } +#endif + if (SUCCEEDED(pIWbemLocator->ConnectServer(_bstr_t(_T("root\\cimv2")), + NULL, NULL, 0L, securityFlag, NULL, NULL, &pIWbemServices))) + { +#if _MSC_VER > 1310 + if (SUCCEEDED(CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, + NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE))) +#endif + { + if (SUCCEEDED(pIWbemServices->ExecQuery(_bstr_t(_T("WQL")), + _bstr_t(query), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumCOMDevs))) + { + while (pEnumCOMDevs && SUCCEEDED(pEnumCOMDevs->Next(10000, 1, &pCOMDev, &uReturned)) && uReturned == 1) + { + CString name; + VARIANT pVal; + VariantInit(&pVal); + + if (pCOMDev->Get(L"Caption", 0L, &pVal, NULL, NULL) == WBEM_S_NO_ERROR && pVal.vt > VT_NULL) + { + name = pVal.bstrVal; + VariantClear(&pVal); +#ifdef UNICODE + name.Replace(_T("™"), _T("")); + name.Replace(_T("®"), _T("")); +#endif + name.Replace(_T("(R)"), _T("")); + name.Replace(_T("Microsoft "), _T("")); + + name.Trim(); + } + VariantInit(&pVal); + + osName = name; + } + } + } + } + } + } + catch (...) + { + + } + + SAFE_RELEASE(pCOMDev); + SAFE_RELEASE(pEnumCOMDevs); + SAFE_RELEASE(pIWbemServices); + SAFE_RELEASE(pIWbemLocator); +} + +#if _MSC_VER <= 1310 +#ifdef UNICODE + +/// https://web.archive.org/web/20050906124319/http://support.microsoft.com/kb/q118626/ +BOOL IsCurrentUserLocalAdministrator(void) +{ + BOOL fReturn = FALSE; + DWORD dwStatus; + DWORD dwAccessMask; + DWORD dwAccessDesired; + DWORD dwACLSize; + DWORD dwStructureSize = sizeof(PRIVILEGE_SET); + PACL pACL = NULL; + PSID psidAdmin = NULL; + + HANDLE hToken = NULL; + HANDLE hImpersonationToken = NULL; + + PRIVILEGE_SET ps; + GENERIC_MAPPING GenericMapping; + + PSECURITY_DESCRIPTOR psdAdmin = NULL; + SID_IDENTIFIER_AUTHORITY SystemSidAuthority = SECURITY_NT_AUTHORITY; + + const DWORD ACCESS_READ = 1; + const DWORD ACCESS_WRITE = 2; + + __try + { + if (!OpenThreadToken(GetCurrentThread(), TOKEN_DUPLICATE | TOKEN_QUERY, + + TRUE, &hToken)) + { + if (GetLastError() != ERROR_NO_TOKEN) + __leave; + + if (!OpenProcessToken(GetCurrentProcess(), + + TOKEN_DUPLICATE | TOKEN_QUERY, &hToken)) + __leave; + } + + if (!DuplicateToken(hToken, SecurityImpersonation, + + &hImpersonationToken)) + __leave; + + if (!AllocateAndInitializeSid(&SystemSidAuthority, 2, + SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, &psidAdmin)) + __leave; + + psdAdmin = LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); + if (psdAdmin == NULL) + __leave; + + if (!InitializeSecurityDescriptor(psdAdmin, + + SECURITY_DESCRIPTOR_REVISION)) + __leave; + + dwACLSize = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + + GetLengthSid(psidAdmin) - sizeof(DWORD); + + pACL = (PACL)LocalAlloc(LPTR, dwACLSize); + if (pACL == NULL) + __leave; + + if (!InitializeAcl(pACL, dwACLSize, ACL_REVISION2)) + __leave; + + dwAccessMask = ACCESS_READ | ACCESS_WRITE; + + if (!AddAccessAllowedAce(pACL, ACL_REVISION2, dwAccessMask, + + psidAdmin)) + __leave; + + if (!SetSecurityDescriptorDacl(psdAdmin, TRUE, pACL, FALSE)) + __leave; + + SetSecurityDescriptorGroup(psdAdmin, psidAdmin, FALSE); + SetSecurityDescriptorOwner(psdAdmin, psidAdmin, FALSE); + + if (!IsValidSecurityDescriptor(psdAdmin)) + __leave; + + dwAccessDesired = ACCESS_READ; + + GenericMapping.GenericRead = ACCESS_READ; + GenericMapping.GenericWrite = ACCESS_WRITE; + GenericMapping.GenericExecute = 0; + GenericMapping.GenericAll = ACCESS_READ | ACCESS_WRITE; + + if (!AccessCheck(psdAdmin, hImpersonationToken, dwAccessDesired, + &GenericMapping, &ps, &dwStructureSize, &dwStatus, + &fReturn)) + { + fReturn = FALSE; + __leave; + } + } + __finally + { + if (pACL) LocalFree(pACL); + if (psdAdmin) LocalFree(psdAdmin); + if (psidAdmin) FreeSid(psidAdmin); + if (hImpersonationToken) CloseHandle(hImpersonationToken); + if (hToken) CloseHandle(hToken); + } + + return fReturn; +} + +/// https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-checktokenmembership +BOOL IsUserAdmin(VOID) +{ + BOOL b = FALSE; + SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; + PSID AdministratorsGroup; + b = AllocateAndInitializeSid( + &NtAuthority, + 2, + SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &AdministratorsGroup); + + if (b) + { + typedef BOOL(WINAPI* FuncCheckTokenMembership)(HANDLE, PSID, PBOOL); + FuncCheckTokenMembership pCheckTokenMembership = NULL; + HMODULE hModule = GetModuleHandle(_T("Advapi32.dll")); + if (hModule) + { + pCheckTokenMembership = (FuncCheckTokenMembership)GetProcAddress(hModule, "CheckTokenMembership"); + } + + if (pCheckTokenMembership != NULL) + { + if (!pCheckTokenMembership(NULL, AdministratorsGroup, &b)) + { + b = FALSE; + } + } + else if (IsCurrentUserLocalAdministrator()) // for NT4 + { + b = TRUE; + } + else + { + b = FALSE; + } + FreeSid(AdministratorsGroup); + } + + return(b); +} +#endif +#endif \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/OsInfoFx.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/OsInfoFx.h new file mode 100644 index 0000000..72f4e42 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/OsInfoFx.h @@ -0,0 +1,242 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once + +////------------------------------------------------ +// OS Info +////------------------------------------------------ + +#if _MSC_VER > 1310 +BOOL IsWindowsVersionOrGreaterFx(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor = 0); +BOOL IsWindowsBuildOrGreater(DWORD build); +BOOL IsX64(); +BOOL IsIa64(); +BOOL IsArm32(); +BOOL IsArm64(); +BOOL IsWow64(); +BOOL IsIe556(); +BOOL IsDotNet2(); +BOOL IsDotNet4(); +BOOL IsDotNet48(); +BOOL IsNT5(); +BOOL IsNT6orLater(); +BOOL IsWin2k(); +BOOL IsWinXpOrLater(); +BOOL IsWinXpLuna(); +BOOL IsWin8orLater(); +BOOL IsWin81orLater(); +BOOL IsDarkModeSupport(); +BOOL HasSidebar(); +#endif + +#if _MSC_VER <= 1310 +#ifdef UNICODE +BOOL IsCurrentUserLocalAdministrator(void); +BOOL IsUserAdmin(VOID); +#endif +#endif + +BOOL IsNT3(); +BOOL IsNT4(); +BOOL IsWin9x(); +BOOL IsWin95(); +BOOL IsWin95First(); +BOOL IsPC98(); +BOOL IsNT51orlater(); +BOOL IsRunningOnWine(); + +DWORD GetIeVersion(); +// DWORD GetWin10Version(); +void GetOsName(CString& osFullName, CString& osName, CString& osVersion, CString& osArchitecture); +void GetOsNameWmi(CString& osFullName); + +////------------------------------------------------ +// Define +////------------------------------------------------ + +#define PRODUCT_UNDEFINED 0x00000000 + +#define PRODUCT_ULTIMATE 0x00000001 +#define PRODUCT_HOME_BASIC 0x00000002 +#define PRODUCT_HOME_PREMIUM 0x00000003 +#define PRODUCT_ENTERPRISE 0x00000004 +#define PRODUCT_HOME_BASIC_N 0x00000005 +#define PRODUCT_BUSINESS 0x00000006 +#define PRODUCT_STANDARD_SERVER 0x00000007 +#define PRODUCT_DATACENTER_SERVER 0x00000008 +#define PRODUCT_SMALLBUSINESS_SERVER 0x00000009 +#define PRODUCT_ENTERPRISE_SERVER 0x0000000A +#define PRODUCT_STARTER 0x0000000B +#define PRODUCT_DATACENTER_SERVER_CORE 0x0000000C +#define PRODUCT_STANDARD_SERVER_CORE 0x0000000D +#define PRODUCT_ENTERPRISE_SERVER_CORE 0x0000000E +#define PRODUCT_ENTERPRISE_SERVER_IA64 0x0000000F +#define PRODUCT_BUSINESS_N 0x00000010 +#define PRODUCT_WEB_SERVER 0x00000011 +#define PRODUCT_CLUSTER_SERVER 0x00000012 +#define PRODUCT_HOME_SERVER 0x00000013 +#define PRODUCT_STORAGE_EXPRESS_SERVER 0x00000014 +#define PRODUCT_STORAGE_STANDARD_SERVER 0x00000015 +#define PRODUCT_STORAGE_WORKGROUP_SERVER 0x00000016 +#define PRODUCT_STORAGE_ENTERPRISE_SERVER 0x00000017 +#define PRODUCT_SERVER_FOR_SMALLBUSINESS 0x00000018 +#define PRODUCT_SMALLBUSINESS_SERVER_PREMIUM 0x00000019 +#define PRODUCT_HOME_PREMIUM_N 0x0000001A +#define PRODUCT_ENTERPRISE_N 0x0000001B +#define PRODUCT_ULTIMATE_N 0x0000001C +#define PRODUCT_WEB_SERVER_CORE 0x0000001D +#define PRODUCT_MEDIUMBUSINESS_SERVER_MANAGEMENT 0x0000001E +#define PRODUCT_MEDIUMBUSINESS_SERVER_SECURITY 0x0000001F +#define PRODUCT_MEDIUMBUSINESS_SERVER_MESSAGING 0x00000020 +#define PRODUCT_SERVER_FOUNDATION 0x00000021 +#define PRODUCT_HOME_PREMIUM_SERVER 0x00000022 +#define PRODUCT_SERVER_FOR_SMALLBUSINESS_V 0x00000023 +#define PRODUCT_STANDARD_SERVER_V 0x00000024 +#define PRODUCT_DATACENTER_SERVER_V 0x00000025 +#define PRODUCT_ENTERPRISE_SERVER_V 0x00000026 +#define PRODUCT_DATACENTER_SERVER_CORE_V 0x00000027 +#define PRODUCT_STANDARD_SERVER_CORE_V 0x00000028 +#define PRODUCT_ENTERPRISE_SERVER_CORE_V 0x00000029 +#define PRODUCT_HYPERV 0x0000002A +#define PRODUCT_STORAGE_EXPRESS_SERVER_CORE 0x0000002B +#define PRODUCT_STORAGE_STANDARD_SERVER_CORE 0x0000002C +#define PRODUCT_STORAGE_WORKGROUP_SERVER_CORE 0x0000002D +#define PRODUCT_STORAGE_ENTERPRISE_SERVER_CORE 0x0000002E +#define PRODUCT_STARTER_N 0x0000002F +#define PRODUCT_PROFESSIONAL 0x00000030 +#define PRODUCT_PROFESSIONAL_N 0x00000031 +#define PRODUCT_SB_SOLUTION_SERVER 0x00000032 +#define PRODUCT_SERVER_FOR_SB_SOLUTIONS 0x00000033 +#define PRODUCT_STANDARD_SERVER_SOLUTIONS 0x00000034 +#define PRODUCT_STANDARD_SERVER_SOLUTIONS_CORE 0x00000035 +#define PRODUCT_SB_SOLUTION_SERVER_EM 0x00000036 +#define PRODUCT_SERVER_FOR_SB_SOLUTIONS_EM 0x00000037 +#define PRODUCT_SOLUTION_EMBEDDEDSERVER 0x00000038 +#define PRODUCT_SOLUTION_EMBEDDEDSERVER_CORE 0x00000039 +#define PRODUCT_PROFESSIONAL_EMBEDDED 0x0000003A +#define PRODUCT_ESSENTIALBUSINESS_SERVER_MGMT 0x0000003B +#define PRODUCT_ESSENTIALBUSINESS_SERVER_ADDL 0x0000003C +#define PRODUCT_ESSENTIALBUSINESS_SERVER_MGMTSVC 0x0000003D +#define PRODUCT_ESSENTIALBUSINESS_SERVER_ADDLSVC 0x0000003E +#define PRODUCT_SMALLBUSINESS_SERVER_PREMIUM_CORE 0x0000003F +#define PRODUCT_CLUSTER_SERVER_V 0x00000040 +#define PRODUCT_EMBEDDED 0x00000041 +#define PRODUCT_STARTER_E 0x00000042 +#define PRODUCT_HOME_BASIC_E 0x00000043 +#define PRODUCT_HOME_PREMIUM_E 0x00000044 +#define PRODUCT_PROFESSIONAL_E 0x00000045 +#define PRODUCT_ENTERPRISE_E 0x00000046 +#define PRODUCT_ULTIMATE_E 0x00000047 +#define PRODUCT_ENTERPRISE_EVALUATION 0x00000048 +#define PRODUCT_MULTIPOINT_STANDARD_SERVER 0x0000004C +#define PRODUCT_MULTIPOINT_PREMIUM_SERVER 0x0000004D +#define PRODUCT_STANDARD_EVALUATION_SERVER 0x0000004F +#define PRODUCT_DATACENTER_EVALUATION_SERVER 0x00000050 +#define PRODUCT_ENTERPRISE_N_EVALUATION 0x00000054 +#define PRODUCT_EMBEDDED_AUTOMOTIVE 0x00000055 +#define PRODUCT_EMBEDDED_INDUSTRY_A 0x00000056 +#define PRODUCT_THINPC 0x00000057 +#define PRODUCT_EMBEDDED_A 0x00000058 +#define PRODUCT_EMBEDDED_INDUSTRY 0x00000059 +#define PRODUCT_EMBEDDED_E 0x0000005A +#define PRODUCT_EMBEDDED_INDUSTRY_E 0x0000005B +#define PRODUCT_EMBEDDED_INDUSTRY_A_E 0x0000005C +#define PRODUCT_STORAGE_WORKGROUP_EVALUATION_SERVER 0x0000005F +#define PRODUCT_STORAGE_STANDARD_EVALUATION_SERVER 0x00000060 +#define PRODUCT_CORE_ARM 0x00000061 +#define PRODUCT_CORE_N 0x00000062 +#define PRODUCT_CORE_COUNTRYSPECIFIC 0x00000063 +#define PRODUCT_CORE_SINGLELANGUAGE 0x00000064 +#define PRODUCT_CORE 0x00000065 +#define PRODUCT_PROFESSIONAL_WMC 0x00000067 +#define PRODUCT_EMBEDDED_INDUSTRY_EVAL 0x00000069 +#define PRODUCT_EMBEDDED_INDUSTRY_E_EVAL 0x0000006A +#define PRODUCT_EMBEDDED_EVAL 0x0000006B +#define PRODUCT_EMBEDDED_E_EVAL 0x0000006C +#define PRODUCT_NANO_SERVER 0x0000006D +#define PRODUCT_CLOUD_STORAGE_SERVER 0x0000006E +#define PRODUCT_CORE_CONNECTED 0x0000006F +#define PRODUCT_PROFESSIONAL_STUDENT 0x00000070 +#define PRODUCT_CORE_CONNECTED_N 0x00000071 +#define PRODUCT_PROFESSIONAL_STUDENT_N 0x00000072 +#define PRODUCT_CORE_CONNECTED_SINGLELANGUAGE 0x00000073 +#define PRODUCT_CORE_CONNECTED_COUNTRYSPECIFIC 0x00000074 +#define PRODUCT_CONNECTED_CAR 0x00000075 +#define PRODUCT_INDUSTRY_HANDHELD 0x00000076 +#define PRODUCT_PPI_PRO 0x00000077 +#define PRODUCT_ARM64_SERVER 0x00000078 +#define PRODUCT_EDUCATION 0x00000079 +#define PRODUCT_EDUCATION_N 0x0000007A +#define PRODUCT_IOTUAP 0x0000007B +#define PRODUCT_CLOUD_HOST_INFRASTRUCTURE_SERVER 0x0000007C +#define PRODUCT_ENTERPRISE_S 0x0000007D +#define PRODUCT_ENTERPRISE_S_N 0x0000007E +#define PRODUCT_PROFESSIONAL_S 0x0000007F +#define PRODUCT_PROFESSIONAL_S_N 0x00000080 +#define PRODUCT_ENTERPRISE_S_EVALUATION 0x00000081 +#define PRODUCT_ENTERPRISE_S_N_EVALUATION 0x00000082 +#define PRODUCT_HOLOGRAPHIC 0x00000087 +#define PRODUCT_HOLOGRAPHIC_BUSINESS 0x00000088 +#define PRODUCT_PRO_SINGLE_LANGUAGE 0x0000008A +#define PRODUCT_PRO_CHINA 0x0000008B +#define PRODUCT_ENTERPRISE_SUBSCRIPTION 0x0000008C +#define PRODUCT_ENTERPRISE_SUBSCRIPTION_N 0x0000008D +#define PRODUCT_DATACENTER_NANO_SERVER 0x0000008F +#define PRODUCT_STANDARD_NANO_SERVER 0x00000090 +#define PRODUCT_DATACENTER_A_SERVER_CORE 0x00000091 +#define PRODUCT_STANDARD_A_SERVER_CORE 0x00000092 +#define PRODUCT_DATACENTER_WS_SERVER_CORE 0x00000093 +#define PRODUCT_STANDARD_WS_SERVER_CORE 0x00000094 +#define PRODUCT_UTILITY_VM 0x00000095 +#define PRODUCT_DATACENTER_EVALUATION_SERVER_CORE 0x0000009F +#define PRODUCT_STANDARD_EVALUATION_SERVER_CORE 0x000000A0 +#define PRODUCT_PRO_WORKSTATION 0x000000A1 +#define PRODUCT_PRO_WORKSTATION_N 0x000000A2 +#define PRODUCT_PRO_FOR_EDUCATION 0x000000A4 +#define PRODUCT_PRO_FOR_EDUCATION_N 0x000000A5 +#define PRODUCT_AZURE_SERVER_CORE 0x000000A8 +#define PRODUCT_AZURE_NANO_SERVER 0x000000A9 +#define PRODUCT_ENTERPRISEG 0x000000AB +#define PRODUCT_ENTERPRISEGN 0x000000AC +#define PRODUCT_SERVERRDSH 0x000000AF +#define PRODUCT_CLOUD 0x000000B2 +#define PRODUCT_CLOUDN 0x000000B3 +#define PRODUCT_HUBOS 0x000000B4 +#define PRODUCT_ONECOREUPDATEOS 0x000000B6 +#define PRODUCT_CLOUDE 0x000000B7 +#define PRODUCT_IOTOS 0x000000B9 +#define PRODUCT_CLOUDEN 0x000000BA +#define PRODUCT_IOTEDGEOS 0x000000BB +#define PRODUCT_IOTENTERPRISE 0x000000BC +#define PRODUCT_LITE 0x000000BD +#define PRODUCT_IOTENTERPRISES 0x000000BF +#define PRODUCT_XBOX_SYSTEMOS 0x000000C0 +#define PRODUCT_XBOX_GAMEOS 0x000000C2 +#define PRODUCT_XBOX_ERAOS 0x000000C3 +#define PRODUCT_XBOX_DURANGOHOSTOS 0x000000C4 +#define PRODUCT_XBOX_SCARLETTHOSTOS 0x000000C5 +#define PRODUCT_XBOX_KEYSTONE 0x000000C6 +#define PRODUCT_AZURE_SERVER_CLOUDHOST 0x000000C7 +#define PRODUCT_AZURE_SERVER_CLOUDMOS 0x000000C8 +#define PRODUCT_CLOUDEDITIONN 0x000000CA +#define PRODUCT_CLOUDEDITION 0x000000CB +#define PRODUCT_AZURESTACKHCI_SERVER_CORE 0x00000196 +#define PRODUCT_DATACENTER_SERVER_AZURE_EDITION 0x00000197 +#define PRODUCT_DATACENTER_SERVER_CORE_AZURE_EDITION 0x00000198 + +#define PRODUCT_UNLICENSED 0xABCDABCD + +// ChatGPT... +#define PRODUCT_ENTERPRISE_G 0x00000067 +#define PRODUCT_PROFESSIONAL_WORKSTATION 0x000000B5 +#define PRODUCT_PROFESSIONAL_WORKSTATION_N 0x000000B6 + +#define SM_TABLETPC 86 +#define SM_MEDIACENTER 87 +#define SM_STARTER 88 +#define SM_SERVERR2 89 diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/ScrollBarFx.cpp b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ScrollBarFx.cpp new file mode 100644 index 0000000..86ec12c --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ScrollBarFx.cpp @@ -0,0 +1,93 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : The MIT License +/*---------------------------------------------------------------------------*/ + +#include "../stdafx.h" +#include "ScrollBarFx.h" +#include "OsInfoFx.h" + +CScrollBarFx::CScrollBarFx() +{ + m_X = 0; + m_Y = 0; + m_BkDC = NULL; + m_RenderMode = SystemDraw; + m_bHighContrast = FALSE; + m_bDarkMode = FALSE; +} + +CScrollBarFx::~CScrollBarFx() +{ + m_BkBrush.DeleteObject(); +} + +IMPLEMENT_DYNAMIC(CScrollBarFx, CScrollBar) + +BEGIN_MESSAGE_MAP(CScrollBarFx, CScrollBar) + //{{AFX_MSG_MAP(CScrollBarFx) + ON_WM_HSCROLL_REFLECT() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +BOOL CScrollBarFx::InitControl(int x, int y, int width, int height, double zoomRatio, CDC* bkDC, int renderMode, BOOL bHighContrast, BOOL bDarkMode, int min, int max, int pos) +{ + m_X = (int)(x * zoomRatio); + m_Y = (int)(y * zoomRatio); + m_CtrlSize.cx = (int)(width * zoomRatio); + m_CtrlSize.cy = (int)(height * zoomRatio); + MoveWindow(m_X, m_Y, m_CtrlSize.cx, m_CtrlSize.cy); + + m_BkDC = bkDC; + m_RenderMode = renderMode; + m_bHighContrast = bHighContrast; + m_bDarkMode = bDarkMode; + + // BkBrush + m_BkBrush.DeleteObject(); + if (bDarkMode) + { + m_BkBrush.CreateSolidBrush(RGB(32, 32, 32)); + } + else + { + m_BkBrush.CreateSolidBrush(RGB(255, 255, 255)); + } + + SetScrollRange(min, max, TRUE); + SetScrollPos(pos); + + Invalidate(); + + return TRUE; +} + +void CScrollBarFx::HScroll(UINT nSBCode, UINT nPos) +{ + int position = GetScrollPos(); + switch (nSBCode) + { + case SB_LINELEFT: + position -= 1; + break; + case SB_LINERIGHT: + position += 1; + break; + case SB_PAGELEFT: + position -= 5; + break; + case SB_PAGERIGHT: + position += 5; + break; + case SB_LEFT: + break; + case SB_RIGHT: + break; + case SB_THUMBTRACK: + position = nPos; + break; + } + SetScrollPos(position); +} \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/ScrollBarFx.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ScrollBarFx.h new file mode 100644 index 0000000..bfa2a2a --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/ScrollBarFx.h @@ -0,0 +1,40 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : The MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once + +#include "CommonFx.h" +#include +#include +#pragma comment(lib, "Gdiplus.lib") +using namespace Gdiplus; + +class CScrollBarFx : public CScrollBar +{ + DECLARE_DYNAMIC(CScrollBarFx) + +public: + CScrollBarFx(); + virtual ~CScrollBarFx(); + BOOL InitControl(int x, int y, int width, int height, double zoomRatio, CDC* bkDC, int renderMode, BOOL bHighContrast, BOOL bDarkMode, int min, int max, int pos); + + BOOL m_bHighContrast; + CBrush m_BkBrush; + +protected: + DECLARE_MESSAGE_MAP() + afx_msg void HScroll(UINT nSBCode, UINT nPos); + + int m_X; + int m_Y; + CSize m_CtrlSize; + CRect m_Margin; + int m_RenderMode; + BOOL m_bDarkMode; + CDC* m_BkDC; +}; + diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/SliderCtrlFx.cpp b/CristalDiskMark/source/CrystalDiskMark/Priscilla/SliderCtrlFx.cpp new file mode 100644 index 0000000..003eab9 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/SliderCtrlFx.cpp @@ -0,0 +1,129 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "SliderCtrlFx.h" +#include "OsInfoFx.h" + +IMPLEMENT_DYNAMIC(CSliderCtrlFx, CSliderCtrl) + +CSliderCtrlFx::CSliderCtrlFx() +{ + m_X = 0; + m_Y = 0; + m_BkDC = NULL; + m_RenderMode = SystemDraw; + m_bHighContrast = FALSE; + m_bDarkMode = FALSE; + m_bBkBitmapInit = FALSE; +} + +CSliderCtrlFx::~CSliderCtrlFx() +{ + m_BkBrush.DeleteObject(); +} + +BEGIN_MESSAGE_MAP(CSliderCtrlFx, CSliderCtrl) + ON_WM_KEYDOWN() +END_MESSAGE_MAP() + +BOOL CSliderCtrlFx::InitControl(int x, int y, int width, int height, double zoomRatio, CDC* bkDC, int renderMode, BOOL bHighContrast, BOOL bDarkMode, int min, int max, int pos) +{ + m_X = (int)(x * zoomRatio); + m_Y = (int)(y * zoomRatio); + m_CtrlSize.cx = (int)(width * zoomRatio); + m_CtrlSize.cy = (int)(height * zoomRatio); + MoveWindow(m_X, m_Y, m_CtrlSize.cx, m_CtrlSize.cy); + SendMessage(TBM_SETTHUMBLENGTH, m_CtrlSize.cy, 0); + + m_BkDC = bkDC; + m_RenderMode = renderMode; + m_bHighContrast = bHighContrast; + m_bDarkMode = bDarkMode; + + SetBkReload(); + LoadCtrlBk(bkDC); + m_BkBrush.DeleteObject(); + if (bDarkMode) + { + // m_BkBrush.CreateSolidBrush(RGB(32, 32, 32)); + m_BkBrush.CreatePatternBrush(&m_BkBitmap); + } + else + { + // m_BkBrush.CreateSolidBrush(RGB(255, 255, 255)); + m_BkBrush.CreatePatternBrush(&m_BkBitmap); + } + + // Range, Pos + SetRange(min, max, TRUE); + SetPos(pos); + + Invalidate(); + + return TRUE; +} + +void CSliderCtrlFx::SetBkReload(void) +{ + m_bBkBitmapInit = FALSE; + m_bBkLoad = FALSE; +} + +void CSliderCtrlFx::LoadCtrlBk(CDC* drawDC) +{ + if (m_bHighContrast) { SetBkReload(); return; } + + if (m_BkBitmap.m_hObject != NULL) + { + BITMAP bitmapInfo; + m_BkBitmap.GetBitmap(&bitmapInfo); + if (bitmapInfo.bmBitsPixel != drawDC->GetDeviceCaps(BITSPIXEL)) + { + SetBkReload(); + } + } + + if (&m_CtrlBitmap != NULL) + { + if (!m_bBkBitmapInit) + { + m_BkBitmap.DeleteObject(); + m_BkBitmap.CreateCompatibleBitmap(drawDC, m_CtrlSize.cx, m_CtrlSize.cy); + m_bBkBitmapInit = TRUE; + } + + if (!m_bBkLoad) + { + CBitmap* pOldBitmap; + CDC* pMemDC = new CDC; + pMemDC->CreateCompatibleDC(drawDC); + pOldBitmap = pMemDC->SelectObject(&m_BkBitmap); + pMemDC->BitBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, m_BkDC, m_X, m_Y, SRCCOPY); + pMemDC->SelectObject(pOldBitmap); + pMemDC->DeleteDC(); + delete pMemDC; + m_bBkLoad = TRUE; + } + } +} + +void CSliderCtrlFx::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) +{ + if (nChar == VK_UP) + { + PostMessage(WM_KEYDOWN, VK_RIGHT, nFlags); + return; + } + else if (nChar == VK_DOWN) + { + PostMessage(WM_KEYDOWN, VK_LEFT, nFlags); + return; + } + + CSliderCtrl::OnKeyDown(nChar, nRepCnt, nFlags); +} \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/SliderCtrlFx.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/SliderCtrlFx.h new file mode 100644 index 0000000..d0dfa43 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/SliderCtrlFx.h @@ -0,0 +1,48 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once + +#include "ImageFx.h" + +class CSliderCtrlFx : public CSliderCtrl +{ + DECLARE_DYNAMIC(CSliderCtrlFx) + +public: + CSliderCtrlFx(); + virtual ~CSliderCtrlFx(); + BOOL InitControl(int x, int y, int width, int height, double zoomRatio, CDC* bkDC, int renderMode, BOOL bHighContrast, BOOL bDarkMode, int min, int max, int pos); + + BOOL m_bHighContrast{}; + CBrush m_BkBrush; + +protected: + // Message Map + DECLARE_MESSAGE_MAP() + afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); + + // Image + void SetBkReload(void); + void LoadCtrlBk(CDC* drawDC); + + int m_X{}; + int m_Y{}; + CSize m_CtrlSize; + CRect m_Margin; + int m_RenderMode{}; + BOOL m_bDarkMode{}; + + // Image + CDC* m_BkDC; + CBitmap m_BkBitmap; + BOOL m_bBkBitmapInit{}; + BOOL m_bBkLoad{}; + CBitmap m_CtrlBitmap; + CImage m_CtrlImage; +}; + diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/StaticFx.cpp b/CristalDiskMark/source/CrystalDiskMark/Priscilla/StaticFx.cpp new file mode 100644 index 0000000..e7c8881 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/StaticFx.cpp @@ -0,0 +1,917 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "StaticFx.h" + +#if _MSC_VER <= 1310 +#define ON_WM_MOUSEHOVER() \ + { 0x2A1 /*WM_MOUSEHOVER*/, 0, 0, 0, AfxSig_vwp, \ + (AFX_PMSG)(AFX_PMSGW) \ + (static_cast< void (AFX_MSG_CALL CWnd::*)(UINT, CPoint) > (OnMouseHover)) }, + +#define ON_WM_MOUSELEAVE() \ + { 0x2A3 /*WM_MOUSELEAVE*/, 0, 0, 0, AfxSig_vv, \ + (AFX_PMSG)(AFX_PMSGW) \ + (static_cast< void (AFX_MSG_CALL CWnd::*)(void) > (OnMouseLeave)) }, +#endif + +////------------------------------------------------ +// CStaticFx +////------------------------------------------------ + +CStaticFx::CStaticFx() +{ + // Control + m_X = 0; + m_Y = 0; + m_RenderMode = SystemDraw; + m_bHighContrast = FALSE; + m_bDarkMode = FALSE; + m_DrawFrame = FALSE; + m_bDrawFrameEx = FALSE; + m_FrameColor = RGB(128, 128, 128); + m_hPal = NULL; + + // Glass + m_GlassColor = RGB(255, 255, 255); + m_GlassAlpha = 255; + + // Meter + m_bMeter = FALSE; + m_MeterRatio = 0.0; + + // Image + m_ImageCount = 0; + m_BkDC = NULL; + m_bBkBitmapInit = FALSE; + m_bBkLoad = FALSE; + + // Font + m_TextAlign = SS_LEFT; + m_TextColor = RGB(0, 0, 0); + + // Mouse + m_bHover = FALSE; + m_bFocas = FALSE; + m_bTrackingNow = FALSE; + m_bHandCursor = FALSE; + + // Text Format + m_TextFormat = 0; + m_LabelFormat = DT_LEFT | DT_TOP | DT_SINGLELINE; + m_UnitFormat = DT_RIGHT | DT_BOTTOM | DT_SINGLELINE; + + // Margin + m_Margin.top = 0; + m_Margin.left = 0; + m_Margin.bottom = 0; + m_Margin.right = 0; +} + +CStaticFx::~CStaticFx() +{ +} + +IMPLEMENT_DYNAMIC(CStaticFx, CStatic) + +BEGIN_MESSAGE_MAP(CStaticFx, CStatic) + //{{AFX_MSG_MAP(CStaticFx) + ON_WM_ERASEBKGND() + ON_WM_MOUSEMOVE() + ON_WM_MOUSEHOVER() + ON_WM_MOUSELEAVE() + ON_WM_KILLFOCUS() + ON_WM_SETFOCUS() + ON_WM_SETCURSOR() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +//------------------------------------------------ +// Control +//------------------------------------------------ + +BOOL CStaticFx::InitControl(int x, int y, int width, int height, double zoomRatio, HPALETTE hPal, CDC* bkDC, + LPCTSTR imagePath, int imageCount, DWORD textAlign, int renderMode, BOOL bHighContrast, BOOL bDarkMode, DWORD drawFrame) +{ + m_X = (int)(x * zoomRatio); + m_Y = (int)(y * zoomRatio); + m_CtrlSize.cx = (int)(width * zoomRatio); + m_CtrlSize.cy = (int)(height * zoomRatio); + MoveWindow(m_X, m_Y, m_CtrlSize.cx, m_CtrlSize.cy); + + m_hPal = hPal; + m_BkDC = bkDC; + m_ImagePath = imagePath; + m_ImageCount = imageCount; + m_RenderMode = renderMode; + + if (SS_LEFT <= textAlign && textAlign <= SS_RIGHT) + { + m_TextAlign = textAlign; + } + + if (m_ToolTip.m_hWnd != NULL) + { + if (m_ToolTip.GetToolCount() != 0) + { + m_ToolTip.DelTool(this, 1); + } + CRect rect; + GetClientRect(rect); + m_ToolTip.AddTool(this, m_ToolTipText, rect, 1); + } + + m_bHighContrast = bHighContrast; + m_bDarkMode = bDarkMode; + m_DrawFrame = drawFrame; + + if (m_bHighContrast) + { + ModifyStyle(SS_OWNERDRAW, m_TextAlign | SS_CENTERIMAGE); + + return TRUE; + } + else if (renderMode & SystemDraw) + { + ModifyStyle(SS_OWNERDRAW, m_TextAlign | SS_CENTERIMAGE); + + return TRUE; + } + else + { + SetBkReload(); + ModifyStyle(m_TextAlign | SS_CENTERIMAGE, SS_OWNERDRAW); + } + + if (renderMode & OwnerDrawImage) + { + if (!LoadBitmap(imagePath)) + { + ModifyStyle(SS_OWNERDRAW, m_TextAlign); + } + } + else + { + m_ImageCount = 1; + m_CtrlImage.Destroy(); + m_CtrlImage.Create(m_CtrlSize.cx, m_CtrlSize.cy * m_ImageCount, 32); + m_CtrlBitmap.Detach(); + m_CtrlBitmap.Attach((HBITMAP)m_CtrlImage); + DWORD length = m_CtrlSize.cx * m_CtrlSize.cy * m_ImageCount * 4; + BYTE* bitmapBits = new BYTE[length]; + m_CtrlBitmap.GetBitmapBits(length, bitmapBits); + + BYTE r, g, b, a; + if (renderMode & OwnerDrawGlass) + { + r = (BYTE)GetRValue(m_GlassColor); + g = (BYTE)GetGValue(m_GlassColor); + b = (BYTE)GetBValue(m_GlassColor); + a = m_GlassAlpha; + } + else // OwnerDrawTransparent + { + r = 0; + g = 0; + b = 0; + a = 0; + } + + for (int y = 0; y < (int)(m_CtrlSize.cy * m_ImageCount); y++) + { + for (int x = 0; x < m_CtrlSize.cx; x++) + { + DWORD p = (y * m_CtrlSize.cx + x) * 4; +#if _MSC_VER > 1310 +#pragma warning( disable : 6386 ) +#endif + bitmapBits[p + 0] = b; + bitmapBits[p + 1] = g; + bitmapBits[p + 2] = r; + bitmapBits[p + 3] = a; +#if _MSC_VER > 1310 +#pragma warning( default : 6386 ) +#endif + } + } + + m_CtrlBitmap.SetBitmapBits(length, bitmapBits); + delete[] bitmapBits; + } + + Invalidate(); + + return TRUE; +} + +void CStaticFx::SetMargin(int top, int left, int bottom, int right, double zoomRatio) +{ + m_Margin.top = (int)(top * zoomRatio); + m_Margin.left = (int)(left * zoomRatio); + m_Margin.bottom = (int)(bottom * zoomRatio); + m_Margin.right = (int)(right * zoomRatio); +} + +CSize CStaticFx::GetSize(void) +{ + return m_CtrlSize; +} + +void CStaticFx::SetDrawFrame(BOOL drawFrame) +{ + if (drawFrame && m_bHighContrast) + { + ModifyStyleEx(0, WS_EX_STATICEDGE, SWP_DRAWFRAME); + } + else + { + ModifyStyleEx(WS_EX_STATICEDGE, 0, SWP_DRAWFRAME); + } + m_DrawFrame = drawFrame; +} + +void CStaticFx::SetDrawFrameEx(BOOL bDrawFrameEx, COLORREF frameColor) +{ + m_bDrawFrameEx = bDrawFrameEx; + m_FrameColor = frameColor; +} + +void CStaticFx::SetGlassColor(COLORREF glassColor, BYTE glassAlpha) +{ + m_GlassColor = glassColor; + m_GlassAlpha = glassAlpha; +} + +void CStaticFx::SetMeter(BOOL bMeter, double meterRatio) +{ + m_bMeter = bMeter; + if (meterRatio > 1.0) + { + m_MeterRatio = 1.0; + } + else if (meterRatio > 0) + { + m_MeterRatio = meterRatio; + } + else + { + m_MeterRatio = 0.0; + } + + Invalidate(); +} + +void CStaticFx::SetLabelUnit(CString label, CString unit) +{ + m_Label = label; + m_Unit = unit; +} + +void CStaticFx::SetLabelUnitFormat(UINT labelFormat, UINT unitFormat) +{ + m_LabelFormat = labelFormat; + m_UnitFormat = unitFormat; +} + +void CStaticFx::SetTextFormat(UINT format) +{ + m_TextFormat = format; +} + +//------------------------------------------------ +// Draw Control +//------------------------------------------------ + +void CStaticFx::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) +{ + CDC* drawDC = CDC::FromHandle(lpDrawItemStruct->hDC); + LoadCtrlBk(drawDC); + + DrawControl(drawDC, lpDrawItemStruct, m_CtrlBitmap, m_BkBitmap, ControlImageNormal); +} + +void CStaticFx::DrawControl(CDC* drawDC, LPDRAWITEMSTRUCT lpDrawItemStruct, CBitmap& ctrlBitmap, CBitmap& bkBitmap, int no) +{ + CDC* pMemDC = new CDC; + CBitmap* pOldMemBitmap; + if(m_hPal && drawDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE) + { + SelectPalette( drawDC->GetSafeHdc(), m_hPal, FALSE ); + drawDC->RealizePalette(); + drawDC->SetStretchBltMode(HALFTONE); + } + pMemDC->CreateCompatibleDC(drawDC); + if(m_hPal && pMemDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE) + { + SelectPalette( pMemDC->GetSafeHdc(), m_hPal, FALSE ); + pMemDC->RealizePalette(); + pMemDC->SetStretchBltMode(HALFTONE); + } + pOldMemBitmap = pMemDC->SelectObject(&ctrlBitmap); + CDC* pBkDC = new CDC; + CBitmap* pOldBkBitmap; + pBkDC->CreateCompatibleDC(drawDC); + if(m_hPal && pBkDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE) + { + SelectPalette( pBkDC->GetSafeHdc(), m_hPal, FALSE ); + pBkDC->RealizePalette(); + pBkDC->SetStretchBltMode(HALFTONE); + } + pOldBkBitmap = pBkDC->SelectObject(&bkBitmap); + + CBitmap DrawBmp; + DrawBmp.CreateCompatibleBitmap(drawDC, m_CtrlSize.cx, m_CtrlSize.cy); + CDC* pDrawBmpDC = new CDC; + CBitmap* pOldDrawBitmap; + pDrawBmpDC->CreateCompatibleDC(drawDC); + if(m_hPal && pDrawBmpDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE) + { + SelectPalette( pDrawBmpDC->GetSafeHdc(), m_hPal, FALSE ); + pDrawBmpDC->RealizePalette(); + pDrawBmpDC->SetStretchBltMode(HALFTONE); + } + pOldDrawBitmap = pDrawBmpDC->SelectObject(&DrawBmp); + + int color = drawDC->GetDeviceCaps(BITSPIXEL) * drawDC->GetDeviceCaps(PLANES); + + if (!m_CtrlImage.IsNull()) + { + if (m_CtrlImage.GetBPP() == 32) + { + CBitmap* bk32Bitmap; + CImage bk32Image; + if (color == 32) + { + bk32Bitmap = &bkBitmap; + } + else + { + bk32Image.Create(m_CtrlSize.cx, m_CtrlSize.cy, 32); + ::StretchBlt(bk32Image.GetDC(), 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, *pBkDC, 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, SRCCOPY); + bk32Bitmap = CBitmap::FromHandle((HBITMAP)bk32Image); + } + + BITMAP CtlBmpInfo, DstBmpInfo; + bk32Bitmap->GetBitmap(&DstBmpInfo); + DWORD DstLineBytes = DstBmpInfo.bmWidthBytes; + DWORD DstMemSize = DstLineBytes * DstBmpInfo.bmHeight; + ctrlBitmap.GetBitmap(&CtlBmpInfo); + DWORD CtlLineBytes = CtlBmpInfo.bmWidthBytes; + DWORD CtlMemSize = CtlLineBytes * CtlBmpInfo.bmHeight; + + if ((DstBmpInfo.bmWidthBytes != CtlBmpInfo.bmWidthBytes) + || (DstBmpInfo.bmHeight != CtlBmpInfo.bmHeight / m_ImageCount)) + { + // Error Check // + } + else + { + BYTE* DstBuffer = new BYTE[DstMemSize]; + bk32Bitmap->GetBitmapBits(DstMemSize, DstBuffer); + BYTE* CtlBuffer = new BYTE[CtlMemSize]; + ctrlBitmap.GetBitmapBits(CtlMemSize, CtlBuffer); + + if (m_bMeter) + { + int meter = (int)(m_CtrlSize.cx * m_MeterRatio); + int baseY; + baseY = m_CtrlSize.cy; + for (LONG py = 0; py < DstBmpInfo.bmHeight; py++) + { + int dn = py * DstLineBytes; + int cn = (baseY + py) * CtlLineBytes; + for (LONG px = 0; px < meter; px++) + { + BYTE a = CtlBuffer[cn + 3]; + BYTE na = 255 - a; + DstBuffer[dn + 0] = (BYTE)((CtlBuffer[cn + 0] * a + DstBuffer[dn + 0] * na) / 255); + DstBuffer[dn + 1] = (BYTE)((CtlBuffer[cn + 1] * a + DstBuffer[dn + 1] * na) / 255); + DstBuffer[dn + 2] = (BYTE)((CtlBuffer[cn + 2] * a + DstBuffer[dn + 2] * na) / 255); + DstBuffer[dn + 3] = 255; + dn += (DstBmpInfo.bmBitsPixel / 8); + cn += (CtlBmpInfo.bmBitsPixel / 8); + } + cn -= baseY * CtlLineBytes; + for (LONG px = meter; px < DstBmpInfo.bmWidth; px++) + { + BYTE a = CtlBuffer[cn + 3]; + BYTE na = 255 - a; + DstBuffer[dn + 0] = (BYTE)((CtlBuffer[cn + 0] * a + DstBuffer[dn + 0] * na) / 255); + DstBuffer[dn + 1] = (BYTE)((CtlBuffer[cn + 1] * a + DstBuffer[dn + 1] * na) / 255); + DstBuffer[dn + 2] = (BYTE)((CtlBuffer[cn + 2] * a + DstBuffer[dn + 2] * na) / 255); + DstBuffer[dn + 3] = 255; + dn += (DstBmpInfo.bmBitsPixel / 8); + cn += (CtlBmpInfo.bmBitsPixel / 8); + } + } + } + else + { + int baseY = m_CtrlSize.cy * no; + for (LONG py = 0; py < DstBmpInfo.bmHeight; py++) + { + int dn = py * DstLineBytes; + int cn = (baseY + py) * CtlLineBytes; + for (LONG px = 0; px < DstBmpInfo.bmWidth; px++) + { +#if _MSC_VER > 1310 +#pragma warning( disable : 6385 ) +#pragma warning( disable : 6386 ) +#endif + BYTE a = CtlBuffer[cn + 3]; + BYTE na = 255 - a; + DstBuffer[dn + 0] = (BYTE)((CtlBuffer[cn + 0] * a + DstBuffer[dn + 0] * na) / 255); + DstBuffer[dn + 1] = (BYTE)((CtlBuffer[cn + 1] * a + DstBuffer[dn + 1] * na) / 255); + DstBuffer[dn + 2] = (BYTE)((CtlBuffer[cn + 2] * a + DstBuffer[dn + 2] * na) / 255); + DstBuffer[dn + 3] = 255; + dn += (DstBmpInfo.bmBitsPixel / 8); + cn += (CtlBmpInfo.bmBitsPixel / 8); +#if _MSC_VER > 1310 +#pragma warning( default : 6386 ) +#pragma warning( default : 6385 ) +#endif + } + } + } + + if (color == 32) + { + DrawBmp.SetBitmapBits(DstMemSize, DstBuffer); + } + else + { + bk32Bitmap->SetBitmapBits(DstMemSize, DstBuffer); + ::StretchBlt(pDrawBmpDC->GetSafeHdc(), 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, bk32Image.GetDC(), 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, SRCCOPY); + bk32Image.ReleaseDC(); + } + DrawString(pDrawBmpDC, lpDrawItemStruct); + drawDC->StretchBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, pDrawBmpDC, 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, SRCCOPY); + + delete[] DstBuffer; + delete[] CtlBuffer; + } + } + else + { + if (m_bMeter) + { + int meter = (int)(m_CtrlSize.cx * (m_MeterRatio)); + pDrawBmpDC->StretchBlt(meter, 0, m_CtrlSize.cx - meter, m_CtrlSize.cy, pMemDC, meter, m_CtrlSize.cy * 0, m_CtrlSize.cx - meter, m_CtrlSize.cy, SRCCOPY); + pDrawBmpDC->StretchBlt(0, 0, meter, m_CtrlSize.cy, pMemDC, 0, m_CtrlSize.cy * 1, meter, m_CtrlSize.cy, SRCCOPY); + DrawString(pDrawBmpDC, lpDrawItemStruct); + drawDC->StretchBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, pDrawBmpDC, 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, SRCCOPY); + } + else + { + pDrawBmpDC->StretchBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, pMemDC, 0, m_CtrlSize.cy* no, m_CtrlSize.cx, m_CtrlSize.cy, SRCCOPY); + DrawString(pDrawBmpDC, lpDrawItemStruct); + drawDC->StretchBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, pDrawBmpDC, 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, SRCCOPY); + } + } + } + else + { + pDrawBmpDC->StretchBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, pBkDC, 0, m_CtrlSize.cy* no, m_CtrlSize.cx, m_CtrlSize.cy, SRCCOPY); + DrawString(pDrawBmpDC, lpDrawItemStruct); + drawDC->StretchBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, pDrawBmpDC, 0, 0, m_CtrlSize.cx, m_CtrlSize.cy, SRCCOPY); + } + + + if (m_DrawFrame == Border::UNDERLINE) + { + HGDIOBJ oldPen; + POINT point; + CPen pen1; + COLORREF frameColor; + if (m_bDarkMode){frameColor = RGB(0x29, 0x2B, 0x2F);} // Windows 11 color + else{ frameColor = RGB(0xCC, 0xCC, 0xCC);} + pen1.CreatePen(PS_SOLID, 1, frameColor); + + oldPen = SelectObject(drawDC->m_hDC, pen1); + MoveToEx(drawDC->m_hDC, 0, m_CtrlSize.cy - 1, &point); + LineTo(drawDC->m_hDC, m_CtrlSize.cx - 1, m_CtrlSize.cy - 1); + LineTo(drawDC->m_hDC, 0, m_CtrlSize.cy - 1); + SelectObject(drawDC->m_hDC, oldPen); + + pen1.DeleteObject(); + } + else if (m_DrawFrame) + { + HGDIOBJ oldPen; + POINT point; + CPen pen1; pen1.CreatePen(PS_SOLID, 1, RGB(0xF8, 0xF8, 0xF8)); + CPen pen2; pen2.CreatePen(PS_SOLID, 1, RGB(0x98, 0x98, 0x98)); + + oldPen = SelectObject(drawDC->m_hDC, pen1); + MoveToEx(drawDC->m_hDC, 0, m_CtrlSize.cy - 1, &point); + LineTo(drawDC->m_hDC, m_CtrlSize.cx - 1, m_CtrlSize.cy - 1); + LineTo(drawDC->m_hDC, m_CtrlSize.cx - 1, 0); + LineTo(drawDC->m_hDC, m_CtrlSize.cx - 1, m_CtrlSize.cy - 1); + SelectObject(drawDC->m_hDC, pen2); + MoveToEx(drawDC->m_hDC, 0, m_CtrlSize.cy - 2, &point); + LineTo(drawDC->m_hDC, 0, 0); + LineTo(drawDC->m_hDC, m_CtrlSize.cx - 1, 0); + SelectObject(drawDC->m_hDC, oldPen); + + pen1.DeleteObject(); + pen2.DeleteObject(); + } + + pDrawBmpDC->SelectObject(&pOldDrawBitmap); + pDrawBmpDC->DeleteDC(); + delete pDrawBmpDC; + pMemDC->SelectObject(&pOldMemBitmap); + pMemDC->DeleteDC(); + delete pMemDC; + pBkDC->SelectObject(&pOldBkBitmap); + pBkDC->DeleteDC(); + delete pBkDC; + + if (m_bDrawFrameEx) + { + CBrush brush; + brush.CreateSolidBrush(m_FrameColor); + drawDC->FrameRect(&(lpDrawItemStruct->rcItem), &brush); + brush.DeleteObject(); + } +} + +void CStaticFx::DrawString(CDC* drawDC, LPDRAWITEMSTRUCT lpDrawItemStruct) +{ + CString title; + GetWindowText(title); + + if (title.IsEmpty()) + { + return; + } + + drawDC->SetBkMode(TRANSPARENT); + CRect rect = (CRect)(lpDrawItemStruct->rcItem); + CRect rectControl = (CRect)(lpDrawItemStruct->rcItem); + rect.top += m_Margin.top; + rect.left += m_Margin.left; + rect.bottom -= m_Margin.bottom; + rect.right -= m_Margin.right; + + CRect rectI; + CSize extent; + HGDIOBJ oldFont = drawDC->SelectObject(m_Font); + if ((m_RenderMode & OwnerDrawTransparent) && m_bDarkMode) + { + SetTextColor(drawDC->m_hDC, RGB(255, 255, 255)); + } + else + { + SetTextColor(drawDC->m_hDC, m_TextColor); + } + extent = drawDC->GetTextExtent(title); + + if (m_bMeter && rect.Width() < extent.cx) + { + title.Replace(_T(","), _T(".")); + int score = _tstoi((LPCTSTR)title); + title.Format(_T("%d"), score); + extent = drawDC->GetTextExtent(title); + } + + if (m_TextFormat != 0) + { + drawDC->DrawText(title, title.GetLength(), rect, m_TextFormat); + drawDC->SelectObject(oldFont); + + oldFont = drawDC->SelectObject(m_FontToolTip); + drawDC->DrawText(m_Label, m_Label.GetLength(), rect, m_LabelFormat); + drawDC->DrawText(m_Unit, m_Unit.GetLength(), rect, m_UnitFormat); + } + else if (!m_Label.IsEmpty()) + { + drawDC->DrawText(title, title.GetLength(), rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + drawDC->SelectObject(oldFont); + + oldFont = drawDC->SelectObject(m_FontToolTip); + drawDC->DrawText(m_Label, m_Label.GetLength(), rect, m_LabelFormat); + drawDC->DrawText(m_Unit, m_Unit.GetLength(), rect, m_UnitFormat); + } + else if (m_TextAlign == SS_LEFT) + { + drawDC->DrawText(title, title.GetLength(), rect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + } + else if (m_TextAlign == SS_RIGHT) + { + drawDC->DrawText(title, title.GetLength(), rect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); + } + else + { + drawDC->DrawText(title, title.GetLength(), rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + } + + drawDC->SelectObject(oldFont); +} + +//------------------------------------------------ +// Image +//------------------------------------------------ + +BOOL CStaticFx::LoadBitmap(LPCTSTR fileName) +{ + if (m_bHighContrast) { return FALSE; } + if (fileName == NULL) { return FALSE; } + + m_CtrlImage.Destroy(); + m_CtrlImage.Load(fileName); + if (m_CtrlImage.IsNull()) { return FALSE; } + + return LoadBitmap((HBITMAP)m_CtrlImage); +} + +BOOL CStaticFx::LoadBitmap(HBITMAP hBitmap) +{ + if (m_bHighContrast) { return FALSE; } + + m_CtrlBitmap.Detach(); + m_CtrlBitmap.Attach(hBitmap); + + return SetBitmap(m_CtrlBitmap); +} + +void CStaticFx::SetBkReload(void) +{ + m_bBkBitmapInit = FALSE; + m_bBkLoad = FALSE; +} + +BOOL CStaticFx::SetBitmap(CBitmap& bitmap) +{ + if (m_bHighContrast) { return FALSE; } + + BITMAP bitmapInfo; + bitmap.GetBitmap(&bitmapInfo); + if (m_CtrlSize.cx != bitmapInfo.bmWidth + || m_CtrlSize.cy != bitmapInfo.bmHeight / m_ImageCount) + { + ModifyStyle(SS_OWNERDRAW, 0); + return FALSE; + } + else + { + ModifyStyle(0, SS_OWNERDRAW); + return TRUE; + } +} + +void CStaticFx::LoadCtrlBk(CDC* drawDC) +{ + if (m_bHighContrast) { SetBkReload(); return; } + + if (m_BkBitmap.m_hObject != NULL) + { + BITMAP bitmapInfo; + m_BkBitmap.GetBitmap(&bitmapInfo); + if (bitmapInfo.bmBitsPixel != drawDC->GetDeviceCaps(BITSPIXEL)) + { + SetBkReload(); + } + } + + if (&m_CtrlBitmap != NULL) + { + if (!m_bBkBitmapInit) + { + m_BkBitmap.DeleteObject(); + m_BkBitmap.CreateCompatibleBitmap(drawDC, m_CtrlSize.cx, m_CtrlSize.cy); + m_bBkBitmapInit = TRUE; + } + + if (!m_bBkLoad) + { + CBitmap* pOldBitmap; + CDC* pMemDC = new CDC; + pMemDC->CreateCompatibleDC(drawDC); + pOldBitmap = pMemDC->SelectObject(&m_BkBitmap); + pMemDC->StretchBlt(0, 0, m_CtrlSize.cx, m_CtrlSize.cy, m_BkDC, m_X, m_Y, m_CtrlSize.cx, m_CtrlSize.cy, SRCCOPY); + pMemDC->SelectObject(pOldBitmap); + pMemDC->DeleteDC(); + delete pMemDC; + m_bBkLoad = TRUE; + } + } +} + +//------------------------------------------------ +// Font +//------------------------------------------------ + +void CStaticFx::SetFontEx(CString face, int size, int sizeToolTip, double zoomRatio, double fontRatio, + COLORREF textColor, LONG fontWeight, BYTE fontRender) +{ + LOGFONT logFont = { 0 }; + logFont.lfCharSet = DEFAULT_CHARSET; + logFont.lfHeight = (LONG)(-1 * size * zoomRatio * fontRatio); + logFont.lfQuality = fontRender; + logFont.lfWeight = fontWeight; + if (face.GetLength() < 32) + { + wsprintf(logFont.lfFaceName, _T("%s"), (LPCTSTR)face); + } + else + { + wsprintf(logFont.lfFaceName, _T("")); + } + + m_Font.DeleteObject(); + m_Font.CreateFontIndirect(&logFont); + SetFont(&m_Font); + + logFont.lfHeight = (LONG)(-1 * sizeToolTip * zoomRatio * fontRatio); + m_FontToolTip.DeleteObject(); + m_FontToolTip.CreateFontIndirect(&logFont); + + m_TextColor = textColor; + + if (m_ToolTip.m_hWnd != NULL) + { + m_ToolTip.SetFont(&m_FontToolTip); + } +} + +//------------------------------------------------ +// Mouse +//------------------------------------------------ + +void CStaticFx::SetHandCursor(BOOL bHandCuror) +{ + m_bHandCursor = bHandCuror; +} + +void CStaticFx::OnMouseMove(UINT nFlags, CPoint point) +{ +#if _MSC_VER <= 1310 + typedef BOOL(WINAPI* Func_TrackMouseEvent)(LPTRACKMOUSEEVENT); + static Func_TrackMouseEvent p_TrackMouseEvent = NULL; + static BOOL bInit_TrackMouseEvent = FALSE; + + if (bInit_TrackMouseEvent && p_TrackMouseEvent == NULL) + { + return; // TrackMouseEvent is not available + } + else + { + HMODULE hModule = GetModuleHandle(_T("user32.dll")); + if (hModule) + { + p_TrackMouseEvent = (Func_TrackMouseEvent)GetProcAddress(hModule, "TrackMouseEvent"); + } + } + + if (p_TrackMouseEvent != NULL) + { + TRACKMOUSEEVENT tme = { sizeof(TRACKMOUSEEVENT) }; + tme.hwndTrack = m_hWnd; + tme.dwFlags = TME_LEAVE | TME_HOVER; + tme.dwHoverTime = 1; + m_bTrackingNow = p_TrackMouseEvent(&tme); + } + bInit_TrackMouseEvent = TRUE; +#else + if (!m_bTrackingNow) + { + TRACKMOUSEEVENT tme = { sizeof(TRACKMOUSEEVENT) }; + tme.hwndTrack = m_hWnd; + tme.dwFlags = TME_LEAVE | TME_HOVER; + tme.dwHoverTime = 1; + m_bTrackingNow = _TrackMouseEvent(&tme); + } +#endif + + CStatic::OnMouseMove(nFlags, point); +} + +void CStaticFx::OnMouseHover(UINT nFlags, CPoint point) +{ +#if _MSC_VER > 1310 + CStatic::OnMouseHover(nFlags, point); +#endif + + m_bHover = TRUE; + Invalidate(); +} + +void CStaticFx::OnMouseLeave() +{ +#if _MSC_VER > 1310 + CStatic::OnMouseLeave(); +#endif + + m_bTrackingNow = FALSE; + m_bHover = FALSE; + Invalidate(); +} + +void CStaticFx::OnSetfocus() +{ + m_bFocas = TRUE; + Invalidate(); +} + +void CStaticFx::OnKillfocus() +{ + m_bFocas = FALSE; + Invalidate(); +} + +BOOL CStaticFx::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) +{ + HCURSOR hCursor = NULL; + if (m_bHandCursor) + { + hCursor = AfxGetApp()->LoadStandardCursor(IDC_HAND); + if (hCursor) + { + ::SetCursor(hCursor); + } + } + else + { + hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW); + if (hCursor) + { + ::SetCursor(hCursor); + } + } + + return TRUE; +} + +//------------------------------------------------ +// ToolTip +//------------------------------------------------ + +void CStaticFx::SetToolTipText(LPCTSTR text) +{ + if (text == NULL) { return; } + + InitToolTip(); + m_ToolTipText = text; + if (m_ToolTip.GetToolCount() == 0) + { + CRect rect; + GetClientRect(rect); + m_ToolTip.AddTool(this, m_ToolTipText, rect, 1); + } + else + { + m_ToolTip.UpdateTipText(m_ToolTipText, this, 1); + } + + SetToolTipActivate(TRUE); +} + +void CStaticFx::SetToolTipActivate(BOOL bActivate) +{ + if (m_ToolTip.GetToolCount() == 0) { return; } + m_ToolTip.Activate(bActivate); +} + +void CStaticFx::SetToolTipWindowText(LPCTSTR pText) +{ + SetToolTipText(pText); + SetWindowText(pText); +} + +CString CStaticFx::GetToolTipText() +{ + return m_ToolTipText; +} + +void CStaticFx::InitToolTip() +{ + if (m_ToolTip.m_hWnd == NULL) + { + m_ToolTip.Create(this, TTS_ALWAYSTIP | TTS_BALLOON | TTS_NOANIMATE | TTS_NOFADE); + m_ToolTip.Activate(FALSE); + m_ToolTip.SetFont(&m_FontToolTip); + m_ToolTip.SendMessage(TTM_SETMAXTIPWIDTH, 0, 1024); + m_ToolTip.SetDelayTime(TTDT_AUTOPOP, 8000); + m_ToolTip.SetDelayTime(TTDT_INITIAL, 500); + m_ToolTip.SetDelayTime(TTDT_RESHOW, 100); + } +} + +BOOL CStaticFx::PreTranslateMessage(MSG* pMsg) +{ + InitToolTip(); + m_ToolTip.RelayEvent(pMsg); + + return CStatic::PreTranslateMessage(pMsg); +} + +BOOL CStaticFx::OnEraseBkgnd(CDC* pDC) +{ + return TRUE; +} \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/StaticFx.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/StaticFx.h new file mode 100644 index 0000000..a339646 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/StaticFx.h @@ -0,0 +1,135 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once + +#include "ImageFx.h" + +class CStaticFx : public CStatic +{ + DECLARE_DYNAMIC(CStaticFx); + + enum Border + { + NO_BORDER = 0, // FALSE + SYSTEM_BORDER = 1, // TRUE + UNDERLINE = 2, + }; + +public: + // Constructors + CStaticFx(); + virtual ~CStaticFx(); + + // Control + BOOL InitControl(int x, int y, int width, int height, double zoomRatio, HPALETTE hPal, CDC* bkDC, + LPCTSTR imagePath, int imageCount, DWORD textAlign, int renderMode, BOOL bHighContrast, BOOL bDarkMode, DWORD drawFrame); + void SetMargin(int top, int left, int bottom, int right, double zoomRatio); + CSize GetSize(void); + void SetDrawFrame(BOOL bDrawFrame); + void SetDrawFrameEx(BOOL bDrawFrame, COLORREF frameColor = RGB(128, 128, 128)); + void SetGlassColor(COLORREF glassColor, BYTE glassAlpha); + void SetMeter(BOOL bMeter, double meterRatio); + void SetLabelUnit(CString label, CString unit); + void SetLabelUnitFormat(UINT labelFormat, UINT unitFormat); + void SetTextFormat(UINT format); + + // Font + void SetFontEx(CString face, int size, int sizeToolTip, double zoomRatio, double fontRatio = 1.0, + COLORREF textColor = RGB(0, 0, 0), LONG fontWeight = FW_NORMAL, BYTE fontRender = CLEARTYPE_NATURAL_QUALITY); + + // ToolTip + void SetToolTipText(LPCTSTR pText); + void SetToolTipActivate(BOOL bActivate = TRUE); + void SetToolTipWindowText(LPCTSTR pText); + CString GetToolTipText(); + + // Mouse + void SetHandCursor(BOOL bHandCuror = TRUE); + +protected: + // Draw Control + virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); + virtual void DrawControl(CDC* drawDC, LPDRAWITEMSTRUCT lpDrawItemStruct, CBitmap& ctrlBitmap, CBitmap& bkBitmap, int no); + virtual void DrawString(CDC* drawDC, LPDRAWITEMSTRUCT lpDrawItemStruct); + + // Image + BOOL LoadBitmap(LPCTSTR fileName); + BOOL LoadBitmap(HBITMAP hBitmap); + void SetBkReload(void); + BOOL SetBitmap(CBitmap& bitmap); + void LoadCtrlBk(CDC* drawDC); + + // ToolTip + void InitToolTip(); + virtual BOOL PreTranslateMessage(MSG* pMsg); + + // Message Map + DECLARE_MESSAGE_MAP() + afx_msg BOOL OnEraseBkgnd(CDC* pDC); + afx_msg void OnMouseMove(UINT nFlags, CPoint point); + afx_msg void OnMouseHover(UINT nFlags, CPoint point); + afx_msg void OnMouseLeave(); + afx_msg void OnKillfocus(); + afx_msg void OnSetfocus(); + afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message); + +protected: + // Control + int m_X; + int m_Y; + CSize m_CtrlSize; + CRect m_Margin; + int m_RenderMode; + BOOL m_bHighContrast; + BOOL m_bDarkMode; + DWORD m_DrawFrame; + BOOL m_bDrawFrameEx; + COLORREF m_FrameColor; + HPALETTE m_hPal; + + CString m_Label; + CString m_Unit; + + UINT m_TextFormat; + UINT m_LabelFormat; + UINT m_UnitFormat; + + // Glass + COLORREF m_GlassColor; + BYTE m_GlassAlpha; + + // Meter + BOOL m_bMeter; + double m_MeterRatio; + + // Image + CString m_ImagePath; + int m_ImageCount; + CDC* m_BkDC; + CBitmap m_BkBitmap; + BOOL m_bBkBitmapInit; + BOOL m_bBkLoad; + CBitmap m_CtrlBitmap; + CImage m_CtrlImage; + + // Font + DWORD m_TextAlign; + CFont m_Font; + CFont m_FontToolTip; + COLORREF m_TextColor; + + // ToolTip + CToolTipCtrl m_ToolTip; + CString m_ToolTipText; + + // Mouse + BOOL m_bHover; + BOOL m_bFocas; + BOOL m_bTrackingNow; + BOOL m_bHandCursor; +}; diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/SystemInfoFx.cpp b/CristalDiskMark/source/CrystalDiskMark/Priscilla/SystemInfoFx.cpp new file mode 100644 index 0000000..483e445 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/SystemInfoFx.cpp @@ -0,0 +1,1887 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "OsInfoFx.h" +#include "SystemInfoFx.h" +#include "UtilityFx.h" + + +//------------------------------------------------ +// Get System Information by WMI +//------------------------------------------------ + +//warning : enum3, enum class +#if _MSC_VER > 1310 +#pragma warning(disable : 26812) +#endif + +#include +#include +#include +#pragma comment(lib, "oleaut32.lib") +#pragma comment(lib, "wbemuuid.lib") + +#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } +#ifndef safeCloseHandle +#define safeCloseHandle(h) { if( h != NULL ) { ::CloseHandle(h); h = NULL; } } +#endif +#ifndef safeVirtualFree +#define safeVirtualFree(h,b,c) { if( h != NULL ) { ::VirtualFree(h, b, c); h = NULL; } } +#endif + +#if _MSC_VER > 1310 +DWORD CountSetBits(ULONG_PTR bitMask) { + DWORD LSHIFT = sizeof(ULONG_PTR) * 8 - 1; + DWORD bitSetCount = 0; + ULONG_PTR bitTest = (ULONG_PTR)1 << LSHIFT; + for (DWORD i = 0; i <= LSHIFT; ++i) { + bitSetCount += ((bitMask & bitTest) ? 1 : 0); + bitTest >>= 1; + } + return bitSetCount; +} + +typedef ULONGLONG(WINAPI* FuncGetLogicalProcessorInformationEx)(LOGICAL_PROCESSOR_RELATIONSHIP, PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD); +typedef BOOL(WINAPI* FuncGetLogicalProcessorInformation)(PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, PDWORD); + +#endif + +void GetProcessorInfo(int* cores, int* threads) +{ + *cores = 0; + *threads = 0; + +#if _MSC_VER > 1310 + HMODULE hModule = GetModuleHandle(_T("kernel32.dll")); + FuncGetLogicalProcessorInformationEx pGetLogicalProcessorInformationEx = NULL; + FuncGetLogicalProcessorInformation pGetLogicalProcessorInformation = NULL; + + if (hModule) + { + pGetLogicalProcessorInformationEx = (FuncGetLogicalProcessorInformationEx)GetProcAddress(hModule, "GetLogicalProcessorInformationEx"); + pGetLogicalProcessorInformation = (FuncGetLogicalProcessorInformation)GetProcAddress(hModule, "GetLogicalProcessorInformation"); + + } + + // for Windows 7 or later + if (pGetLogicalProcessorInformationEx != NULL) + { + DWORD length = 0; + pGetLogicalProcessorInformationEx(RelationAll, nullptr, &length); + + BYTE* buffer = new BYTE[length]; + if (pGetLogicalProcessorInformationEx(RelationAll, reinterpret_cast(buffer), &length)) { + DWORD offset = 0; + while (offset < length) { + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX info = reinterpret_cast(buffer + offset); + + if (info->Relationship == RelationProcessorCore) { + *cores += 1; + for (int group = 0; group < info->Processor.GroupCount; ++group) { + *threads += CountSetBits(info->Processor.GroupMask[group].Mask); + } + } + + offset += info->Size; + } + } + delete[] buffer; + } + // for Windows XP SP3/Vista + else if(pGetLogicalProcessorInformation != NULL) + { + DWORD length = 0; + pGetLogicalProcessorInformation(NULL, &length); + + SYSTEM_LOGICAL_PROCESSOR_INFORMATION* buffer = new SYSTEM_LOGICAL_PROCESSOR_INFORMATION[length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)]; + pGetLogicalProcessorInformation(&buffer[0], &length); + + for (DWORD i = 0; i != length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); ++i) { + if (buffer[i].Relationship == RelationProcessorCore) { + *cores += 1; + ULONG_PTR mask = buffer[i].ProcessorMask; + while (mask) { + if (mask & 1) { + *threads += 1; + } + mask >>= 1; + } + } + } + delete[] buffer; + } + else // - Windows XP SP2 + { + SYSTEM_INFO si = { 0 }; + GetSystemInfo(&si); + + *cores = si.dwNumberOfProcessors; + *threads = si.dwNumberOfProcessors; + } +#else + SYSTEM_INFO si = {0}; + GetSystemInfo(&si); + + *cores = si.dwNumberOfProcessors; + *threads = si.dwNumberOfProcessors; +#endif +} + +#if defined(_M_IX86) || defined(_M_X64) + +void getProcessorBrandString(char* brandString); +void getCpuName(char* cpuName); +unsigned int getCacheInfoIntel(int test); +CStringA getCpuModelName(CStringA vendor, unsigned int family, unsigned int model, unsigned int stepping, unsigned int type, unsigned int L2Cashe, unsigned int fpu); + +#if _MSC_VER > 1310 +#include // for __cpuid + +void GetCpuid(unsigned int param, unsigned int* _eax, unsigned int* _ebx, unsigned int* _ecx, unsigned int* _edx) +{ + int cpuInfo[4] = { 0 }; + __cpuid(cpuInfo, param); + + *_eax = cpuInfo[0]; + *_ebx = cpuInfo[1]; + *_ecx = cpuInfo[2]; + *_edx = cpuInfo[3]; +} +#else +BOOL IsCpuidSupported() +{ + bool supported = false; + __asm { + pushfd + pop eax + mov ebx, eax + xor eax, 0x200000 + push eax + popfd + pushfd + pop eax + xor eax, ebx + test eax, 0x200000 + jz not_supported + mov supported, 1 + not_supported: + push ebx + popfd + } + + return supported; +} + +void GetCpuid(unsigned int param, unsigned int* _eax, unsigned int* _ebx, unsigned int* _ecx, unsigned int* _edx) +{ + static BOOL bIsCpuid = IsCpuidSupported(); + if (!bIsCpuid) { return; } + + unsigned int a, b, c, d; + __asm { + MOV EAX, param + CPUID + MOV a, EAX + MOV b, EBX + MOV c, ECX + MOV d, EDX + } + *_eax = a; + *_ebx = b; + *_ecx = c; + *_edx = d; +} +#endif + +void getProcessorBrandString(char* brandString) +{ + unsigned int eax = 0; + unsigned int ebx = 0; + unsigned int ecx = 0; + unsigned int edx = 0; + + __try + { + // Check if CPUID supports brand string + GetCpuid(0x80000000, &eax, &ebx, &ecx, &edx); + if (eax < 0x80000004) { + strcpy(brandString, ""); + return; + } + + // Get brand string + for (int i = 0x80000002; i <= 0x80000004; ++i) { + GetCpuid(i, &eax, &ebx, &ecx, &edx); + memcpy(brandString + (i - 0x80000002) * 16, &eax, 4); + memcpy(brandString + (i - 0x80000002) * 16 + 4, &ebx, 4); + memcpy(brandString + (i - 0x80000002) * 16 + 8, &ecx, 4); + memcpy(brandString + (i - 0x80000002) * 16 + 12, &edx, 4); + } + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + strcpy(brandString, ""); + } +} + +void GetHypervisorVendorString(char* vendorString) +{ + unsigned int eax = 0; + unsigned int ebx = 0; + unsigned int ecx = 0; + unsigned int edx = 0; + + __try + { + GetCpuid(0x40000000, &eax, &ebx, &ecx, &edx); + memcpy(vendorString, &ebx, 4); + memcpy(vendorString + 4, &ecx, 4); + memcpy(vendorString + 8, &edx, 4); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + strcpy(vendorString, ""); + } +} + +#if _MSC_VER <= 1310 + +BOOL Is486orAbove(); +BOOL IsCyrixCPU(); +BOOL IsAmd486(); +BOOL readCcr(BYTE addr, BYTE* value); +CStringA getCyrixModelName(); + +inline unsigned char inpb(unsigned short port) +{ + unsigned char val; + __asm { + mov dx, port + in al, dx + mov val, al + } + return val; +} + +inline void outpb(unsigned short port, unsigned char data) +{ + __asm { + mov dx, port + mov al, data + out dx, al + } +} + +// https://github.com/captainys/TOWNSEMU/issues/147#issuecomment-2764633838 +BOOL IsFMTOWNS() +{ + static BOOL b = -1; + if (b == -1) + { + b = FALSE; + + if (IsPC98()) + { + return FALSE; + } + + if (! IsWin95First()) + { + return FALSE; + } + + if (GetUserDefaultLCID() != 0x0411) // Japanese + { + return FALSE; + } + + __try + { + BYTE in30 = (BYTE)inpb(0x30); + BYTE in31 = (BYTE)inpb(0x31); + if (in30 && in30 != 0xFF && in31 && in31 != 0xFF) + { + b = TRUE; + return TRUE; + } + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return FALSE; + } + } + + return b; +} + +BOOL Is486orAbove() +{ + BOOL result = FALSE; + + __asm { + pushfd + pop eax + mov ecx, eax + xor eax, 0x40000 + push eax + popfd + + pushfd + pop eax + cmp eax, ecx + jz is_not_80486 + + mov result, 1 + jmp end + + is_not_80486 : + mov result, 0 + end : + } + + return result; +} + +BOOL IsCyrixCPU() +{ + BOOL result = FALSE; + + __asm { + xor ax, ax + sahf + mov ax, 5 + mov bx, 2 + div bl + lahf + cmp ah, 2 + jne not_cyrix + + mov result, 1 + jmp end + + not_cyrix: + mov result, 0 + end: + } + + return result; +} + +BOOL IsAmd486() +{ + __try + { + outpb(0xC0, 0x55); + BYTE val = (BYTE)inpb(0xC0); + if (val == 0x55) + { + return TRUE; + } + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return FALSE; + } + + return FALSE; +} + +BOOL readCcr(BYTE addr, BYTE* value) +{ + __try + { + outpb(0x22, addr); + *value = inpb(0x23); + return TRUE; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return FALSE; + } +} + +// http://www.bitsavers.org/components/cyrix/appnotes/Cyrix_CPU_Detection_Guide_1997.pdf +CStringA getCyrixModelName() +{ + BYTE dir0 = 0; + + if(! readCcr(0xFE, &dir0)) + { + return "Cyrix Unknown CPU"; + } + if (0x00 <= dir0 && dir0 <= 0x1F) { return "Cyrix Cx486"; } + else if (0x20 <= dir0 && dir0 <= 0x2F) { return "Cyrix 5x86"; } + else if (0x30 <= dir0 && dir0 <= 0x3F) { return "Cyrix 6x86"; } + else if (0x40 <= dir0 && dir0 <= 0x4F) { return "Cyrix MediaGX"; } + else if (0x50 <= dir0 && dir0 <= 0x5F) { return "Cyrix 6x86MX/MII"; } + else + { + return "Cyrix Unknown CPU"; + } +} + +#endif + +void getCpuName(char* cpuName) +{ + unsigned int eax = 0; + unsigned int ebx = 0; + unsigned int ecx = 0; + unsigned int edx = 0; + unsigned int maxCpuId = 0; + +#if _MSC_VER <= 1310 + if(! IsCpuidSupported()) + { + CStringA modelName = ""; + + if (! Is486orAbove()) + { + modelName = "386 Generation Processor"; + } + else if (IsFMTOWNS()) + { + + } + else if (IsCyrixCPU()) + { + modelName = "Cyrix Unknown CPU"; + if (IsWin9x() && !IsPC98() && !IsFMTOWNS()) // PC-98 and FM TOWNS are not supported. + { + modelName = getCyrixModelName(); + } + } + else if (IsWin9x()) + { + if (IsAmd486()) + { + modelName = "AMD Am486/Am5x86"; + } + } + + if (modelName.IsEmpty()) + { + SYSTEM_INFO si = { 0 }; + GetSystemInfo(&si); + + switch (si.dwProcessorType) + { + case PROCESSOR_INTEL_386: + modelName = "386 Generation Processor"; + break; + case PROCESSOR_INTEL_486: + modelName = "486 Generation Processor"; + break; + case PROCESSOR_INTEL_PENTIUM: + modelName = "586 Generation Processor"; + break; + case PROCESSOR_ARCHITECTURE_UNKNOWN: + default: + modelName = "Unknown Processor"; + break; + } + } + + sprintf(cpuName, "%s", modelName.GetString()); + return; + } +#endif + + char vendorString[13] = {0}; + GetCpuid(0x00, &eax, &ebx, &ecx, &edx); + memcpy(vendorString, &ebx, 4); + memcpy(vendorString + 4, &edx, 4); + memcpy(vendorString + 8, &ecx, 4); + + GetCpuid(0x80000000, &eax, &ebx, &ecx, &edx); + maxCpuId = eax; + + if (maxCpuId >= 0x80000004) + { + getProcessorBrandString(cpuName); + } + else + { + CStringA vendor = vendorString; + GetCpuid(0x1, &eax, &ebx, &ecx, &edx); + + unsigned int version = eax; + unsigned int family = (version >> 8) & 0xF; + unsigned int model = (version >> 4) & 0xF; + unsigned int stepping = version & 0xF; + unsigned int type = (version >> 12) & 0x3; + + unsigned int fpu = 1; // Under Construction + + unsigned int cacheL2 = 0; + unsigned int temp = 0; + if (vendor.Find("GenuineIntel") == 0) + { + GetCpuid(0x2, &eax, &ebx, &ecx, &edx); + temp = getCacheInfoIntel((eax & 0xFF000000) >> 24); if (temp > 0) { cacheL2 = temp; } + temp = getCacheInfoIntel((eax & 0x00FF0000) >> 16); if (temp > 0) { cacheL2 = temp; } + temp = getCacheInfoIntel((eax & 0x0000FF00) >> 8); if (temp > 0) { cacheL2 = temp; } + temp = getCacheInfoIntel((eax & 0x000000FF) >> 0); if (temp > 0) { cacheL2 = temp; } + temp = getCacheInfoIntel((ebx & 0xFF000000) >> 24); if (temp > 0) { cacheL2 = temp; } + temp = getCacheInfoIntel((ebx & 0x00FF0000) >> 16); if (temp > 0) { cacheL2 = temp; } + temp = getCacheInfoIntel((ebx & 0x0000FF00) >> 8); if (temp > 0) { cacheL2 = temp; } + temp = getCacheInfoIntel((ebx & 0x000000FF) >> 0); if (temp > 0) { cacheL2 = temp; } + temp = getCacheInfoIntel((ecx & 0xFF000000) >> 24); if (temp > 0) { cacheL2 = temp; } + temp = getCacheInfoIntel((ecx & 0x00FF0000) >> 16); if (temp > 0) { cacheL2 = temp; } + temp = getCacheInfoIntel((ecx & 0x0000FF00) >> 8); if (temp > 0) { cacheL2 = temp; } + temp = getCacheInfoIntel((ecx & 0x000000FF) >> 0); if (temp > 0) { cacheL2 = temp; } + temp = getCacheInfoIntel((edx & 0xFF000000) >> 24); if (temp > 0) { cacheL2 = temp; } + temp = getCacheInfoIntel((edx & 0x00FF0000) >> 16); if (temp > 0) { cacheL2 = temp; } + temp = getCacheInfoIntel((edx & 0x0000FF00) >> 8); if (temp > 0) { cacheL2 = temp; } + temp = getCacheInfoIntel((edx & 0x000000FF) >> 0); if (temp > 0) { cacheL2 = temp; } + } + + if(maxCpuId >= 0x80000006) + { + GetCpuid(0x80000006, &eax, &ebx, &ecx, &edx); + cacheL2 = ((ebx >> 12) & 0xF) * 256; + } + + CStringA modelName = getCpuModelName(vendor, family, model, stepping, type, cacheL2, fpu); + + sprintf(cpuName, "%s", modelName.GetString()); + } +} + +unsigned int getCacheInfoIntel(int test) +{ + unsigned int CacheL2 = 0; + switch (test) + { + case 0x39:CacheL2 = 128;break; + case 0x3A:CacheL2 = 192;break; + case 0x3B:CacheL2 = 128;break; + case 0x3C:CacheL2 = 256;break; + case 0x3D:CacheL2 = 384;break; + case 0x3E:CacheL2 = 512;break; + case 0x41:CacheL2 = 128;break; + case 0x42:CacheL2 = 256;break; + case 0x43:CacheL2 = 512;break; + case 0x44:CacheL2 = 1024;break; + case 0x45:CacheL2 = 2048;break; + case 0x48:CacheL2 = 3072;break; + case 0x49:CacheL2 = 4096;break; + case 0x4E:CacheL2 = 6144;break; + case 0x78:CacheL2 = 1024;break; + case 0x79:CacheL2 = 128;break; + case 0x7A:CacheL2 = 256;break; + case 0x7B:CacheL2 = 512;break; + case 0x7C:CacheL2 = 1024;break; + case 0x7D:CacheL2 = 2048;break; + case 0x7E:CacheL2 = 256;break; + case 0x7F:CacheL2 = 512;break; + case 0x81:CacheL2 = 128;break; + case 0x82:CacheL2 = 256;break; + case 0x83:CacheL2 = 512;break; + case 0x84:CacheL2 = 1024;break; + case 0x85:CacheL2 = 2048;break; + case 0x86:CacheL2 = 512;break; + case 0x87:CacheL2 = 1024;break; + default:; + } + + return CacheL2; +} + +CStringA getCpuModelName(CStringA vendor, unsigned int family, unsigned int model, unsigned int stepping, unsigned int type, unsigned int cacheL2, unsigned int fpu) +{ + CStringA modelName; + int F = family; + int M = model; + int S = stepping; + int CacheL2 = cacheL2; + char* n = NULL; + char* v = NULL; + + if (vendor.Find("GenuineIntel") == 0) + { + v = "Intel"; + switch (family) + { + case 0x06: + switch (model) + { + ///////////// + // Cascades + ///////////// + case 0xA: + n = "Pentium III Xeon"; + break; + /////////// + // Banias + /////////// + case 9: + if (CacheL2 == 1024) { + n = "Pentium M"; + } else { + n = "Celeron M"; + } + break; + /////////////// + // Coppermine + /////////////// + case 8: + if (CacheL2 >= 256) { + n = "Pentium III"; + } else if (CacheL2 <= 128) { + n = "Celeron"; + } + break; + /////////// + // Katmai + /////////// + case 7: + if (CacheL2 == 1024) { + n = "Pentium III Xeon"; + } else { + n = "Pentium III"; + } + break; + ////////////////////// + // Dixon & Mendocino + ////////////////////// + case 6: + if ((S == 0xA || S == 0xD) && CacheL2 == 256) { + n = "Mobile Pentium II"; + } else if ((S == 0xA || S == 0xD) && CacheL2 == 128) { + n = "Mobile Celeron"; + } else { + n = "Celeron"; + } + break; + ////////////// + // Deschutes + ////////////// + case 5: + if (CacheL2 > 512) { + n = "Pentium II Xeon"; + } else if (CacheL2 == 512 && type == 0x1) { + n = "Pentium II OverDrive"; + } else if (CacheL2 == 512) { + n = "Pentium II"; + } else if (CacheL2 == 0) { + n = "Celeron"; + } + break; + case 4: + n = "OverDrive"; + break; + case 3: + if (type == 0x1) { + n = "Pentium II OverDrive"; + } else { + n = "Pentium II"; + } + break; + /////// + // P6 + /////// + case 2: + n = "Pentium Pro"; + break; + case 1: + n = "Pentium Pro"; + break; + case 0: + n = "Pentium Pro"; + break; + } + break; + //////////// + // Pentium + //////////// + case 5: + switch (model) { + case 8: + n = "Pentium MMX"; + break; + case 7: + n = "Pentium"; + break; + case 6: + n = "Pentium OverDrive"; + break; + case 5: + n = "Pentium OverDrive"; + break; + case 4: + if (type == 1) { + n = "Pentium MMX OverDrive"; + } else { + n = "Pentium MMX"; + } + break; + case 3: + n = "Pentium OverDrive"; + break; + case 2: + if (type == 1) { + n = "Pentium OverDrive"; + } else { + n = "Pentium"; + } + break; + case 1: + case 0: + if (type == 1) { + n = "Pentium OverDrive"; + } else { + n = "Pentium"; + } + break; + default: + n = "Pentium"; + break; + } + break; + //////// + // 486 + //////// + case 4: + switch (model) { + case 9: + n = "486DX4 WB"; + break; + case 8: + n = "486DX4"; + break; + case 7: + n = "486DX2 WB"; + break; + case 5: + n = "486SX2"; + break; + case 4: + n = "486SL"; + break; + case 3: + n = "486DX2"; + break; + case 2: + n = "486SX"; + break; + case 1: + case 0: + n = "486DX"; + break; + default: + n = "486"; + break; + } + break; + case 3: + n = "386"; + break; + default: + n = "Unknown CPU"; + break; + } + } + else if (vendor.Find("AuthenticAMD") == 0) + { + v = "AMD"; + switch (family) + { + case 5: + switch (model) { + case 0xD: + case 0xC: + if (CacheL2 == 256) { + n = "K6-III+"; + } else { + n = "K6-2+"; + } + break; + case 0xA: + n = "Geode LX"; + break; + case 9: + n = "K6-III"; + break; + case 8: + n = "K6-2"; + break; + case 7: + n = "K6"; + break; + case 3: + case 2: + case 1: + case 0: + n = "K5"; + break; + default: + n = "Unknown CPU"; + break; + } + break; + case 4: + switch (model) { + case 0xF: + case 0xE: + n = "Am5x86"; + break; + case 9: + case 8: + n = "Am486DX4/Am5x86"; + break; + case 7: + case 3: + if (fpu) + { + n = "Am486DX2"; + } + else + { + n = "Am486SX2"; + } + break; + default: + n = "Unknown CPU"; + break; + } + break; + default: + n = "Unknown CPU"; + break; + } + } + else if (vendor.Find("CentaurHauls") == 0 || vendor.Find("VIA VIA VIA") == 0) + { + v = ""; + switch (family) + { + case 6: + switch (model) + { + case 0xF: + n = "VIA Nano"; + break; + case 0xD: + case 0xC: + case 0xB: + case 0xA: + n = "VIA C7"; + break; + case 9: + case 8: + case 7: + n = "VIA C3"; + break; + case 6: + n = "VIA Cyrix III"; + break; + case 5: + if (stepping == 0) + { + n = "VIA 6x86MX"; + } + else + { + n = "VIA Cyrix III"; + } + break; + case 4: + n = "VIA Cyrix III"; + break; + case 3: + n = "VIA 6x86MX"; + break; + case 2: + n = "VIA Cyrix III"; + break; + case 0: + n = "VIA 6x86MX"; + break; + default: + n = "VIA Unknown CPU"; + break; + } + break; + case 5: + switch (model) + { + case 9: + n = "IDT WinChip 3"; + break; + case 8: + n = "IDT WinChip 2"; + break; + case 4: + n = "IDT WinChip C6"; + break; + default: + n = "VIA Unknown CPU"; + break; + } + break; + default: + n = "VIA Unknown CPU"; + break; + } + } + else if (vendor.Find("CyrixInstead") == 0) + { + v = "Cyrix"; + switch (family) + { + case 6: + switch (model) + { + case 0: + n = "MII"; + break; + default: + n = "Unknown CPU"; + break; + } + break; + case 5: + switch (model) + { + case 9: + n = "Geode"; + break; + case 4: + n = "MediaGX"; + break; + case 2: + n = "6x86"; + break; + default: + n = "Unknown CPU"; + break; + } + break; + case 4: + switch (model) + { + case 4: + n = "MediaGX"; + break; + default: + n = "Unknown CPU"; + break; + } + break; + default: + n = "Unknown CPU"; + break; + } + } + else if (vendor.Find("SiS SiS SiS") == 0) + { + v = "SiS"; + n = "55x"; + } + else if (vendor.Find("Geode by NSC") == 0) + { + v = "NSC"; + n = "Geode"; + } + else if (vendor.Find("NexGenDriven") == 0) + { + v = "NexGen"; + n = "Nx586"; + } + else if (vendor.Find("RiseRiseRise") == 0) + { + v = "Rise"; + n = "mP6"; + } + else if (vendor.Find("UMC UMC UMC") == 0) + { + v = "UMC"; + n = "Green CPU"; + } + else + { + v = NULL; + n = NULL; + } + + if (v != NULL && n != NULL) + { + modelName.Format("%s %s", v, n); + return modelName; + } + else + { + return ""; + } +} +#endif + +void GetCpuInfo(CString& cpuInfo, CString& cpuName, int* clock, int* cores, int* threads) +{ + CString query = _T("Select * from Win32_Processor"); + + IWbemLocator* pIWbemLocator = NULL; + IWbemServices* pIWbemServices = NULL; + IEnumWbemClassObject* pEnumCOMDevs = NULL; + IWbemClassObject* pCOMDev = NULL; + ULONG uReturned = 0; + BOOL flag = FALSE; + + try + { + if (SUCCEEDED(CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, + IID_IWbemLocator, (LPVOID*)&pIWbemLocator))) + { + long securityFlag = 0; +#if _MSC_VER > 1310 + if (IsWindowsVersionOrGreaterFx(6, 0)) { securityFlag = WBEM_FLAG_CONNECT_USE_MAX_WAIT; } +#endif + if (SUCCEEDED(pIWbemLocator->ConnectServer(_bstr_t(_T("root\\cimv2")), + NULL, NULL, 0L, securityFlag, NULL, NULL, &pIWbemServices))) + { +#if _MSC_VER > 1310 + if (SUCCEEDED(CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, + NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE))) +#endif + { + if (SUCCEEDED(pIWbemServices->ExecQuery(_bstr_t(_T("WQL")), + _bstr_t(query), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumCOMDevs))) + { + while (pEnumCOMDevs && SUCCEEDED(pEnumCOMDevs->Next(10000, 1, &pCOMDev, &uReturned)) && uReturned == 1) + { + CString name; + UINT32 speed = 0; + // UINT32 cores = 0; + // UINT32 threads = 0; + +#if defined(_M_IX86) || defined(_M_X64) + char brandString[49] = { 0 }; + getCpuName(brandString); + //getProcessorBrandString(brandString); + name = brandString; + name.TrimLeft(); + name.TrimRight(); + cpuName = name; +#endif + VARIANT pVal; + VariantInit(&pVal); + if (name.IsEmpty() && pCOMDev->Get(L"Name", 0L, &pVal, NULL, NULL) == WBEM_S_NO_ERROR && pVal.vt > VT_NULL) + { + name = pVal.bstrVal; + name.TrimLeft(); + name.TrimRight(); + cpuName = name; + VariantClear(&pVal); + } + if (pCOMDev->Get(L"MaxClockSpeed", 0L, &pVal, NULL, NULL) == WBEM_S_NO_ERROR && pVal.vt > VT_NULL) + { + speed = pVal.intVal; + if(clock != NULL) { *clock = (int)speed; } + VariantClear(&pVal); + } + + /* + if (pCOMDev->Get(L"NumberOfCores", 0L, &pVal, NULL, NULL) == WBEM_S_NO_ERROR && pVal.vt > VT_NULL) + { + *cores = pVal.intVal; + VariantClear(&pVal); + } + if (pCOMDev->Get(L"NumberOfLogicalProcessors", 0L, &pVal, NULL, NULL) == WBEM_S_NO_ERROR && pVal.vt > VT_NULL) + { + *threads = pVal.intVal; + VariantClear(&pVal); + } + */ + + GetProcessorInfo(cores, threads); + + if (speed > 0 && *cores > 0 && *threads > 0) + { + if (*cores > 1 && *threads > 1) + { + cpuInfo.Format(_T("%s %dMHz (%dcores/%dthreads)"), (LPCTSTR)name, speed, *cores, *threads); + } + else if (*cores == 1 && *threads == 1) + { + cpuInfo.Format(_T("%s %dMHz (%dcore/%dthread)"), (LPCTSTR)name, speed, *cores, *threads); + } + else if (*cores == 1) + { + cpuInfo.Format(_T("%s %dMHz (%dcore/%dthreads)"), (LPCTSTR)name, speed, *cores, *threads); + } + } + else if (*cores > 0 && *threads > 0) + { + if (*cores > 1 && *threads > 1) + { + cpuInfo.Format(_T("%s (%dcores/%dthreads)"), (LPCTSTR)name, *cores, *threads); + } + else if (*cores == 1 && *threads == 1) + { + cpuInfo.Format(_T("%s (%dcore/%dthread)"), (LPCTSTR)name, *cores, *threads); + } + else if (*cores == 1) + { + cpuInfo.Format(_T("%s (%dcore/%dthreads)"), (LPCTSTR)name, *cores, *threads); + } + } + else + { + cpuInfo = name; + } + + break; + } + } + } + } + } + } + catch (...) + { + + } + + SAFE_RELEASE(pCOMDev); + SAFE_RELEASE(pEnumCOMDevs); + SAFE_RELEASE(pIWbemServices); + SAFE_RELEASE(pIWbemLocator); + + if (cpuInfo.IsEmpty()) + { + TCHAR str[256] = {}; + DWORD value = 0; + DWORD type = REG_SZ; + ULONG size = 256 * sizeof(TCHAR); + HKEY hKey = NULL; + +#if defined(_M_IX86) || defined(_M_X64) + char brandString[49] = { 0 }; + getCpuName(brandString); + //getProcessorBrandString(brandString); + cpuInfo = brandString; + cpuInfo.TrimLeft(); + cpuInfo.TrimRight(); +#endif + + if (cpuInfo.IsEmpty() && !IsWin9x()) + { + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"), 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + if (RegQueryValueEx(hKey, _T("ProcessorNameString"), NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS) + { + cpuInfo = str; + cpuInfo.TrimLeft(); + cpuInfo.TrimRight(); + } + } + } + +#ifdef _M_IX86 + +#endif + cpuName = cpuInfo; + + if (!IsWin9x()) + { + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"), 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + type = REG_DWORD; + size = sizeof(DWORD); + if (RegQueryValueEx(hKey, _T("~MHz"), NULL, &type, (LPBYTE)&value, &size) == ERROR_SUCCESS) + { + cpuInfo.Format(_T("%s %dMHz"), (LPCTSTR)cpuInfo, value); + if (clock != NULL) { *clock = (int)value; } + } + RegCloseKey(hKey); + } + } + + GetProcessorInfo(cores, threads); + if (*cores > 0 && *threads > 0) + { + CString cstr; + if (*cores > 1 && *threads > 1) + { + cstr.Format(_T(" (%dcores/%dthreads)"), *cores, *threads); + } + else if(*cores == 1 && *threads == 1) + { + cstr.Format(_T(" (%dcore/%dthread)"), *cores, *threads); + } + else if (*cores == 1) + { + cstr.Format(_T(" (%dcore/%dthreads)"), *cores, *threads); + } + + cpuInfo += cstr; + } + } + + cpuName.Replace(_T("(R)"), _T("")); + cpuName.Replace(_T("(r)"), _T("")); + cpuName.Replace(_T("(TM)"), _T("")); + cpuName.Replace(_T("(tm)"), _T("")); + cpuInfo.Replace(_T("(R)"), _T("")); + cpuInfo.Replace(_T("(TM)"), _T("")); + cpuInfo.Replace(_T("(t)"), _T("")); + cpuInfo.Replace(_T("(tm)"), _T("")); +} + +void GetGpuInfo(CString& gpuInfo) +{ +#if _MSC_VER > 1310 + #pragma comment(lib, "dxgi.lib") + + HMODULE hModule = LoadLibraryEx(_T("dxgi.dll"), NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + typedef HRESULT(WINAPI* FuncCreateDXGIFactory)(REFIID, void**); + FuncCreateDXGIFactory pCreateDXGIFactory = NULL; + + if (hModule != NULL) + { + pCreateDXGIFactory = (FuncCreateDXGIFactory)GetProcAddress(hModule, "CreateDXGIFactory"); + } + + if (pCreateDXGIFactory != NULL) + { + IDXGIFactory* pFactory = nullptr; + if (SUCCEEDED(pCreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&pFactory))) + { + IDXGIAdapter* pAdapter = nullptr; + DXGI_ADAPTER_DESC adapterDesc; + + for (UINT i = 0; pFactory->EnumAdapters(i, &pAdapter) != DXGI_ERROR_NOT_FOUND; ++i) + { + pAdapter->GetDesc(&adapterDesc); + + CString cstr; + cstr.Format(L"%s [%dMB]", adapterDesc.Description, (int)(adapterDesc.DedicatedVideoMemory / (1024 * 1024))); + + if (cstr.Find(L"Microsoft Basic Render Driver") == 0) + { + pAdapter->Release(); + continue; + } + + if (gpuInfo.IsEmpty()) + { + gpuInfo = cstr; + } + else if (gpuInfo.Find(cstr) >= 0) + { + // Duplication + } + else + { + gpuInfo += _T(" | ") + cstr; + } + pAdapter->Release(); + } + pFactory->Release(); + } + } + + if (! gpuInfo.IsEmpty()) + { + gpuInfo.Replace(_T("(R)"), _T("")); + gpuInfo.Replace(_T("(TM)"), _T("")); + return; + } +#endif + + CString query = _T("Select * from Win32_VideoController"); + + IWbemLocator* pIWbemLocator = NULL; + IWbemServices* pIWbemServices = NULL; + IEnumWbemClassObject* pEnumCOMDevs = NULL; + IWbemClassObject* pCOMDev = NULL; + ULONG uReturned = 0; + BOOL flag = FALSE; + + try + { + if (SUCCEEDED(CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, + IID_IWbemLocator, (LPVOID*)&pIWbemLocator))) + { + long securityFlag = 0; +#if _MSC_VER > 1310 + if (IsWindowsVersionOrGreaterFx(6, 0)) { securityFlag = WBEM_FLAG_CONNECT_USE_MAX_WAIT; } +#endif + if (SUCCEEDED(pIWbemLocator->ConnectServer(_bstr_t(_T("root\\cimv2")), + NULL, NULL, 0L, securityFlag, NULL, NULL, &pIWbemServices))) + { +#if _MSC_VER > 1310 + if (SUCCEEDED(CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, + NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE))) +#endif + { + if (SUCCEEDED(pIWbemServices->ExecQuery(_bstr_t(_T("WQL")), + _bstr_t(query), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumCOMDevs))) + { + while (pEnumCOMDevs && SUCCEEDED(pEnumCOMDevs->Next(10000, 1, &pCOMDev, &uReturned)) && uReturned == 1) + { + CString name; + + VARIANT pVal; + VariantInit(&pVal); + if (pCOMDev->Get(L"Name", 0L, &pVal, NULL, NULL) == WBEM_S_NO_ERROR && pVal.vt > VT_NULL) + { + name = pVal.bstrVal; + name.TrimLeft(); + name.TrimRight(); + VariantClear(&pVal); + } + + // exclusion + // if (name.Find(_T("Virtual")) >= 0 || name.Find(_T("Remote")) >= 0 || name.Find(_T("ASPEED")) == 0) + if (gpuInfo.IsEmpty()) + { + gpuInfo = name; + } + else + { + gpuInfo += _T(" | ") + name; + } + } + } + } + } + } + } + catch (...) + { + + } + + SAFE_RELEASE(pCOMDev); + SAFE_RELEASE(pEnumCOMDevs); + SAFE_RELEASE(pIWbemServices); + SAFE_RELEASE(pIWbemLocator); + +#ifndef UNICODE + if (IsWin9x()) + { + TCHAR str[256] = { 0 }; + GetPrivateProfileStringFx(_T("boot.description"), _T("display.drv"), _T(""), str, 256, _T("system.ini")); + gpuInfo = str; + } +#endif + + if (gpuInfo.IsEmpty()) + { + TCHAR str[256] = {}; + DWORD value = 0; + DWORD type = REG_SZ; + ULONG size = 256 * sizeof(TCHAR); + HKEY hKey = NULL; + + // GPU + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SYSTEM\\CurrentControlSet\\Control\\Class\\{4d36e968-e325-11ce-bfc1-08002be10318}\\0000"), 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + type = REG_SZ; + size = 256 * sizeof(TCHAR); + if (RegQueryValueEx(hKey, _T("DriverDesc"), NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS) + { + gpuInfo = str; + gpuInfo.TrimLeft(); + gpuInfo.TrimRight(); + } + RegCloseKey(hKey); + } + if (gpuInfo.IsEmpty()) + { + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SYSTEM\\CurrentControlSet\\Control\\Class\\{4d36e968-e325-11ce-bfc1-08002be10318}\\0001"), 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + type = REG_SZ; + size = 256 * sizeof(TCHAR); + if (RegQueryValueEx(hKey, _T("DriverDesc"), NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS) + { + gpuInfo = str; + gpuInfo.TrimLeft(); + gpuInfo.TrimRight(); + } + RegCloseKey(hKey); + } + } + if (gpuInfo.IsEmpty()) + { + TCHAR str[256] = {}; + DWORD value = 0; + DWORD type = REG_SZ; + ULONG size = 256 * sizeof(TCHAR); + HKEY hKey = NULL; + HKEY hKey2 = NULL; + type = REG_SZ; + size = 256 * sizeof(TCHAR); + CString videoKey, key; + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DEVICEMAP\\VIDEO"), 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + for (int i = 0; i < 10; i++) + { + videoKey.Format(_T("\\Device\\Video%d"), i); + key = _T(""); + if (RegQueryValueEx(hKey, videoKey, NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS) + { + key = str; + } + + if (!key.IsEmpty()) + { + key.MakeLower().Replace(_T("\\registry\\machine\\"), _T("")); + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hKey2) == ERROR_SUCCESS) + { + type = REG_SZ; + size = 256 * sizeof(TCHAR); + if (RegQueryValueEx(hKey2, _T("DriverDesc"), NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS) + { + CString cstr = str; + cstr.TrimLeft(); + cstr.TrimRight(); + + if (gpuInfo.IsEmpty()) + { + gpuInfo = cstr; + } + else + { + gpuInfo += _T(" | ") + cstr; + } + RegCloseKey(hKey2); + break; + } + + type = REG_BINARY; + if (RegQueryValueEx(hKey2, _T("HardwareInformation.AdapterString"), NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS) + { + #ifndef UNICODE + char buf[256] = {}; + WideCharToMultiByte(CP_ACP, 0, (WCHAR*)str, size, buf, 256, 0, 0); + CString cstr = buf; + #else + CString cstr = str; + #endif + cstr.TrimLeft(); + cstr.TrimRight(); + + if (gpuInfo.IsEmpty()) + { + gpuInfo = cstr; + } + else + { + gpuInfo += _T(" | ") + cstr; + } + RegCloseKey(hKey2); + break; + } + else + { + _tcscpy(str, key); + #if _MSC_VER > 1310 + PathRemoveFileSpec(str); + CString cstr = PathFindFileName(str); + #else + PathRemoveFileSpecFx(str); + CString cstr = PathFindFileNameFx(str); + #endif + cstr += _T(" compatible device"); + if (gpuInfo.IsEmpty()) + { + gpuInfo = cstr; + } + else + { + gpuInfo += _T(" | ") + cstr; + } + RegCloseKey(hKey2); + break; + } + } + } + } + RegCloseKey(hKey); + } + } + } + + gpuInfo.Replace(_T("(R)"), _T("")); + gpuInfo.Replace(_T("(TM)"), _T("")); +} + +void GetBaseBoardInfo(CString& baseBoardInfo) +{ + CString query = _T("Select * from Win32_BaseBoard"); + + IWbemLocator* pIWbemLocator = NULL; + IWbemServices* pIWbemServices = NULL; + IEnumWbemClassObject* pEnumCOMDevs = NULL; + IWbemClassObject* pCOMDev = NULL; + ULONG uReturned = 0; + BOOL flag = FALSE; + + try + { + if (SUCCEEDED(CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, + IID_IWbemLocator, (LPVOID*)&pIWbemLocator))) + { + long securityFlag = 0; +#if _MSC_VER > 1310 + if (IsWindowsVersionOrGreaterFx(6, 0)) { securityFlag = WBEM_FLAG_CONNECT_USE_MAX_WAIT; } +#endif + if (SUCCEEDED(pIWbemLocator->ConnectServer(_bstr_t(_T("root\\cimv2")), + NULL, NULL, 0L, securityFlag, NULL, NULL, &pIWbemServices))) + { +#if _MSC_VER > 1310 + if (SUCCEEDED(CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, + NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE))) +#endif + { + if (SUCCEEDED(pIWbemServices->ExecQuery(_bstr_t(_T("WQL")), + _bstr_t(query), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumCOMDevs))) + { + while (pEnumCOMDevs && SUCCEEDED(pEnumCOMDevs->Next(10000, 1, &pCOMDev, &uReturned)) && uReturned == 1) + { + CString manufacturer; + CString product; + + VARIANT pVal; + VariantInit(&pVal); + if (pCOMDev->Get(L"Manufacturer", 0L, &pVal, NULL, NULL) == WBEM_S_NO_ERROR && pVal.vt > VT_NULL) + { + manufacturer = pVal.bstrVal; + manufacturer.TrimLeft(); + manufacturer.TrimRight(); + VariantClear(&pVal); + } + if (pCOMDev->Get(L"Product", 0L, &pVal, NULL, NULL) == WBEM_S_NO_ERROR && pVal.vt > VT_NULL) + { + product = pVal.bstrVal; + product.TrimLeft(); + product.TrimRight(); + VariantClear(&pVal); + } + baseBoardInfo = manufacturer + _T(" ") + product; + + baseBoardInfo.Replace(_T("To Be Filled By O.E.M."), _T("")); + baseBoardInfo.Replace(_T("To be filled by O.E.M."), _T("")); + baseBoardInfo.Replace(_T("Not Available"), _T("")); + baseBoardInfo.TrimLeft(); + baseBoardInfo.TrimRight(); + } + } + } + } + } + } + catch (...) + { + + } + + SAFE_RELEASE(pCOMDev); + SAFE_RELEASE(pEnumCOMDevs); + SAFE_RELEASE(pIWbemServices); + SAFE_RELEASE(pIWbemLocator); + + if (baseBoardInfo.IsEmpty()) + { + TCHAR str[256] = {}; + DWORD value = 0; + DWORD type = REG_SZ; + ULONG size = 256 * sizeof(TCHAR); + HKEY hKey = NULL; + + // BaseBoard/System + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DESCRIPTION\\System\\BIOS"), 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + size = 256 * sizeof(TCHAR); + if (RegQueryValueEx(hKey, _T("BaseBoardManufacturer"), NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS) + { + baseBoardInfo = str; + + } + size = 256 * sizeof(TCHAR); + if (RegQueryValueEx(hKey, _T("BaseBoardProduct"), NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS) + { + baseBoardInfo += _T(" "); + baseBoardInfo += str; + } + + baseBoardInfo.Replace(_T("To Be Filled By O.E.M."), _T("")); + baseBoardInfo.Replace(_T("To be filled by O.E.M."), _T("")); + baseBoardInfo.Replace(_T("Not Available"), _T("")); + baseBoardInfo.TrimLeft(); + baseBoardInfo.TrimRight(); + + RegCloseKey(hKey); + } + } +} + +void GetComputerSystemInfo(CString& computerSystemInfo) +{ + CString query = _T("Select * from Win32_ComputerSystem"); + + IWbemLocator* pIWbemLocator = NULL; + IWbemServices* pIWbemServices = NULL; + IEnumWbemClassObject* pEnumCOMDevs = NULL; + IWbemClassObject* pCOMDev = NULL; + ULONG uReturned = 0; + BOOL flag = FALSE; + + try + { + if (SUCCEEDED(CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, + IID_IWbemLocator, (LPVOID*)&pIWbemLocator))) + { + long securityFlag = 0; +#if _MSC_VER > 1310 + if (IsWindowsVersionOrGreaterFx(6, 0)) { securityFlag = WBEM_FLAG_CONNECT_USE_MAX_WAIT; } +#endif + if (SUCCEEDED(pIWbemLocator->ConnectServer(_bstr_t(_T("root\\cimv2")), + NULL, NULL, 0L, securityFlag, NULL, NULL, &pIWbemServices))) + { +#if _MSC_VER > 1310 + if (SUCCEEDED(CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, + NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE))) +#endif + { + if (SUCCEEDED(pIWbemServices->ExecQuery(_bstr_t(_T("WQL")), + _bstr_t(query), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumCOMDevs))) + { + while (pEnumCOMDevs && SUCCEEDED(pEnumCOMDevs->Next(10000, 1, &pCOMDev, &uReturned)) && uReturned == 1) + { + CString manufacturer; + CString model; + + VARIANT pVal; + VariantInit(&pVal); + if (pCOMDev->Get(L"Manufacturer", 0L, &pVal, NULL, NULL) == WBEM_S_NO_ERROR && pVal.vt > VT_NULL) + { + manufacturer = pVal.bstrVal; + manufacturer.TrimLeft(); + manufacturer.TrimRight(); + VariantClear(&pVal); + } + if (pCOMDev->Get(L"Model", 0L, &pVal, NULL, NULL) == WBEM_S_NO_ERROR && pVal.vt > VT_NULL) + { + model = pVal.bstrVal; + model.TrimLeft(); + model.TrimRight(); + VariantClear(&pVal); + } + computerSystemInfo = manufacturer + _T(" ") + model; + + computerSystemInfo.Replace(_T("To Be Filled By O.E.M."), _T("")); + computerSystemInfo.Replace(_T("To be filled by O.E.M."), _T("")); + computerSystemInfo.Replace(_T("System manufacturer"), _T("")); + computerSystemInfo.Replace(_T("System Product Name"), _T("")); + computerSystemInfo.Replace(_T("Not Available"), _T("")); + computerSystemInfo.TrimLeft(); + computerSystemInfo.TrimRight(); + } + } + } + } + } + } + catch (...) + { + + } + + SAFE_RELEASE(pCOMDev); + SAFE_RELEASE(pEnumCOMDevs); + SAFE_RELEASE(pIWbemServices); + SAFE_RELEASE(pIWbemLocator); + + if (computerSystemInfo.IsEmpty()) + { + TCHAR str[256] = {}; + DWORD value = 0; + DWORD type = REG_SZ; + ULONG size = 256 * sizeof(TCHAR); + HKEY hKey = NULL; + + // BaseBoard/System + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DESCRIPTION\\System\\BIOS"), 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + size = 256 * sizeof(TCHAR); + if (RegQueryValueEx(hKey, _T("SystemManufacturer"), NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS) + { + computerSystemInfo = str; + } + size = 256 * sizeof(TCHAR); + if (RegQueryValueEx(hKey, _T("SystemProductName"), NULL, &type, (LPBYTE)str, &size) == ERROR_SUCCESS) + { + computerSystemInfo += _T(" "); + computerSystemInfo += str; + } + + computerSystemInfo.Replace(_T("To Be Filled By O.E.M."), _T("")); + computerSystemInfo.Replace(_T("To be filled by O.E.M."), _T("")); + computerSystemInfo.Replace(_T("Not Available"), _T("")); + computerSystemInfo.Replace(_T("System Product Name"), _T("")); + computerSystemInfo.Replace(_T("System manufacturer"), _T("")); + computerSystemInfo.TrimLeft(); + computerSystemInfo.TrimRight(); + + RegCloseKey(hKey); + } + } + +#if _MSC_VER <= 1310 + if (IsPC98()) + { + computerSystemInfo = _T("[PC-98] ") + computerSystemInfo; + } + else if (IsFMTOWNS()) + { + computerSystemInfo = _T("[FM TOWNS] ") + computerSystemInfo; + } +#endif +} + +void GetScreenInfo(CString& screenInfo, int* width, int* height, int* color, CString& smoothing) +{ + int screenWidth = GetSystemMetrics(SM_CXSCREEN); + int screenHeight = GetSystemMetrics(SM_CYSCREEN); + if(width != NULL){*width = screenWidth;} + if(height != NULL){*height = screenHeight;} + + HDC hDC = ::GetDC(NULL); + int bitsPerPixel = ::GetDeviceCaps(hDC, BITSPIXEL); + int planes = ::GetDeviceCaps(hDC, PLANES); + int colorDepth = bitsPerPixel * planes; + if(color != NULL){*color = colorDepth;} + ::ReleaseDC(NULL, hDC); + + DWORD fontSmoothingType = 0; + SystemParametersInfoW(SPI_GETFONTSMOOTHINGTYPE, 0, &fontSmoothingType, 0); + screenInfo.Format(_T("%dx%d %dbit"), screenWidth, screenHeight, colorDepth); + + if (fontSmoothingType == FE_FONTSMOOTHINGSTANDARD) + { + screenInfo += _T(" (Smoothing)"); + smoothing = _T("Smoothing"); + } + else if (fontSmoothingType == FE_FONTSMOOTHINGCLEARTYPE) + { + screenInfo += _T(" (ClearType)"); + smoothing = _T("ClearType"); + } +} + +void GetMemoryInfo(CString& memoryInfo, int* size) +{ +#if _MSC_VER > 1310 + MEMORYSTATUSEX memory = {}; + memory.dwLength = sizeof(memory); + GlobalMemoryStatusEx(&memory); + + if ((int)(memory.ullTotalPhys / 1024 / 1024 / 1024 + 1) > 1) + { + memoryInfo.Format(_T("%.1fGB"), (memory.ullTotalPhys / 1024.0 / 1024 / 1024)); + + } + else + { + memoryInfo.Format(_T("%dMB"), (int)(memory.ullTotalPhys / 1024.0 / 1024 + 1)); + } + if(size != NULL){*size = (int)(memory.ullTotalPhys / 1024.0 / 1024 + 1);} +#else + + // for Windows 2000 + typedef BOOL(WINAPI* FuncGlobalMemoryStatusEx)(LPMEMORYSTATUSEX); + HMODULE hModule = GetModuleHandle(_T("kernel32.dll")); + FuncGlobalMemoryStatusEx pGlobalMemoryStatusEx = NULL; + if (hModule) + { + pGlobalMemoryStatusEx = (FuncGlobalMemoryStatusEx)GetProcAddress(hModule, "GlobalMemoryStatusEx"); + } + + if (pGlobalMemoryStatusEx != NULL) // for Windows 2000 + { + MEMORYSTATUSEX memory; + memory.dwLength = sizeof(memory); + pGlobalMemoryStatusEx(&memory); + + if ((int)(memory.ullTotalPhys / 1024 / 1024 / 1024 + 1) > 1) + { + memoryInfo.Format(_T("%.1fGB"), (memory.ullTotalPhys / 1024.0 / 1024 / 1024)); + + } + else + { + memoryInfo.Format(_T("%dMB"), (int)(memory.ullTotalPhys / 1024.0 / 1024 + 1)); + } + if (size != NULL) { *size = (int)(memory.ullTotalPhys / 1024.0 / 1024 + 1); } + } + else + { + MEMORYSTATUS memory; + memory.dwLength = sizeof(memory); + GlobalMemoryStatus(&memory); + + if ((int)(memory.dwTotalPhys / 1024 / 1024 / 1024 + 1) > 1) + { + memoryInfo.Format(_T("%.1fGB"), (memory.dwTotalPhys / 1024.0 / 1024 / 1024)); + + } + else + { + memoryInfo.Format(_T("%dMB"), (int)(memory.dwTotalPhys / 1024.0 / 1024 + 1)); +} + if (size != NULL) { *size = (int)(memory.dwTotalPhys / 1024.0 / 1024 + 1); } + } +#endif +} + +#if _MSC_VER <= 1310 +/// https://www.betaarchive.com/wiki/index.php/Microsoft_KB_Archive/124207 +BOOL IsCoProcessorPresent() +{ +#define MY_ERROR_WRONG_OS 0x20000000 + HKEY hKey; + SYSTEM_INFO SystemInfo; + + // return FALSE if we are not running under Windows NT + // this should be expanded to cover alternative Win32 platforms + + if (!(GetVersion() & 0x7FFFFFFF)) + { + SetLastError(MY_ERROR_WRONG_OS); + return(FALSE); + } + + // we return TRUE if we're not running on x86 + // other CPUs have built in floating-point, with no registry entry + + GetSystemInfo(&SystemInfo); + + if ((SystemInfo.dwProcessorType != PROCESSOR_INTEL_386) && + (SystemInfo.dwProcessorType != PROCESSOR_INTEL_486) && + (SystemInfo.dwProcessorType != PROCESSOR_INTEL_PENTIUM)) + { + return(TRUE); + } + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DESCRIPTION\\System\\FloatingPointProcessor"), + 0, KEY_EXECUTE, &hKey) != ERROR_SUCCESS) + { + // GetLastError() will indicate ERROR_RESOURCE_DATA_NOT_FOUND + // if we can't find the key. This indicates no coprocessor present + return(FALSE); + } + + RegCloseKey(hKey); + return(TRUE); +} +#endif + diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/SystemInfoFx.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/SystemInfoFx.h new file mode 100644 index 0000000..81d3572 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/SystemInfoFx.h @@ -0,0 +1,30 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once + +//------------------------------------------------ +// System Info +//------------------------------------------------ + +void GetCpuInfo(CString& cpuInfo, CString& cpuName, int* clock, int* cores, int* threads); +void GetGpuInfo(CString& gpuInfo); +void GetBaseBoardInfo(CString& baseBoardInfo); +void GetComputerSystemInfo(CString& computerSystemInfo); +void GetScreenInfo(CString& screenInfo, int* width, int* height, int* color, CString& smoothing); +void GetMemoryInfo(CString& screenInfo, int* size = NULL); + +#if defined(_M_IX86) || defined(_M_X64) +void GetCpuid(unsigned int param, unsigned int* _eax, unsigned int* _ebx, unsigned int* _ecx, unsigned int* _edx); +void GetHypervisorVendorString(char* vendorString); +#endif + +#if _MSC_VER <= 1310 +/// https://www.betaarchive.com/wiki/index.php/Microsoft_KB_Archive/124207 +BOOL IsCoProcessorPresent(); +BOOL IsFMTOWNS(); +#endif \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/UAHMenuBar.cpp b/CristalDiskMark/source/CrystalDiskMark/Priscilla/UAHMenuBar.cpp new file mode 100644 index 0000000..abf129d --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/UAHMenuBar.cpp @@ -0,0 +1,205 @@ +/*---------------------------------------------------------------------------*/ +// Author : adzm +// Web : https://github.com/adzm/win32-custom-menubar-aero-theme +// License : MIT License +// https://github.com/adzm/win32-custom-menubar-aero-theme/blob/main/LICENSE +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include +#include +#include "UAHMenuBar.h" + +#pragma comment(lib, "uxtheme.lib") + +static HTHEME g_menuTheme = nullptr; +extern bool g_darkModeEnabled; + +// ugly colors for illustration purposes +static HBRUSH g_brBarBackground = CreateSolidBrush(RGB(0x2C, 0x2C, 0x2C)); + +void UAHDrawMenuNCBottomLine(HWND hWnd) +{ + MENUBARINFO mbi = { sizeof(mbi) }; + if (!GetMenuBarInfo(hWnd, OBJID_MENU, 0, &mbi)) + { + return; + } + + RECT rcClient = { 0 }; + GetClientRect(hWnd, &rcClient); + MapWindowPoints(hWnd, nullptr, (POINT*)&rcClient, 2); + + RECT rcWindow = { 0 }; + GetWindowRect(hWnd, &rcWindow); + + OffsetRect(&rcClient, -rcWindow.left, -rcWindow.top); + + // the rcBar is offset by the window rect + RECT rcAnnoyingLine = rcClient; + rcAnnoyingLine.bottom = rcAnnoyingLine.top; + rcAnnoyingLine.top--; + + + HDC hdc = GetWindowDC(hWnd); + FillRect(hdc, &rcAnnoyingLine, g_brBarBackground); + ReleaseDC(hWnd, hdc); +} + +// processes messages related to UAH / custom menubar drawing. +// return true if handled, false to continue with normal processing in your wndproc +bool UAHWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT* lr) +{ + if (! g_darkModeEnabled) + { + return false; + } + + switch (message) + { + case WM_UAHDRAWMENU: + { + UAHMENU* pUDM = (UAHMENU*)lParam; + RECT rc = { 0 }; + + // get the menubar rect + { + MENUBARINFO mbi = { sizeof(mbi) }; + GetMenuBarInfo(hWnd, OBJID_MENU, 0, &mbi); + + RECT rcWindow; + GetWindowRect(hWnd, &rcWindow); + + // the rcBar is offset by the window rect + rc = mbi.rcBar; + OffsetRect(&rc, -rcWindow.left, -rcWindow.top); + } + + FillRect(pUDM->hdc, &rc, g_brBarBackground); + + return true; + } + case WM_UAHDRAWMENUITEM: + { + typedef HRESULT(WINAPI* FuncDrawThemeTextEx)(HTHEME hTheme, HDC hdc, + int iPartId, int iStateId, LPCWSTR pszText, int iCharCount, DWORD dwTextFlags, + LPRECT pRect, const DTTOPTS* pOptions); + + static bool bInitialized = false; + static FuncDrawThemeTextEx pDrawThemeTextEx = NULL; + + if (bInitialized == false) + { + HMODULE hModule = GetModuleHandle(_T("UxTheme.dll")); + if (hModule) + { + pDrawThemeTextEx = (FuncDrawThemeTextEx)GetProcAddress(hModule, "DrawThemeTextEx"); + } + bInitialized = true; + } + + if (pDrawThemeTextEx == NULL) + { + return false; + } + + UAHDRAWMENUITEM* pUDMI = (UAHDRAWMENUITEM*)lParam; + + // ugly colors for illustration purposes + static HBRUSH g_brItemBackground = CreateSolidBrush(RGB(0x2C, 0x2C, 0x2C)); + static HBRUSH g_brItemBackgroundHot = CreateSolidBrush(RGB(0x35, 0x35, 0x35)); + static HBRUSH g_brItemBackgroundSelected = CreateSolidBrush(RGB(0x35, 0x35, 0x35)); + + HBRUSH* pbrBackground = &g_brItemBackground; + + // get the menu item string + wchar_t menuString[256] = { 0 }; + MENUITEMINFO mii = { sizeof(mii), MIIM_STRING }; + { + mii.dwTypeData = menuString; + mii.cch = (sizeof(menuString) / 2) - 1; + + GetMenuItemInfo(pUDMI->um.hmenu, pUDMI->umi.iPosition, TRUE, &mii); + } + + // get the item state for drawing + + DWORD dwFlags = DT_CENTER | DT_SINGLELINE | DT_VCENTER; + + int iTextStateID = 0; + int iBackgroundStateID = 0; + { + if ((pUDMI->dis.itemState & ODS_INACTIVE) | (pUDMI->dis.itemState & ODS_DEFAULT)) { + // normal display + iTextStateID = MPI_NORMAL; + iBackgroundStateID = MPI_NORMAL; + } + if (pUDMI->dis.itemState & ODS_HOTLIGHT) { + // hot tracking + iTextStateID = MPI_HOT; + iBackgroundStateID = MPI_HOT; + + pbrBackground = &g_brItemBackgroundHot; + } + if (pUDMI->dis.itemState & ODS_SELECTED) { + // clicked -- MENU_POPUPITEM has no state for this, though MENU_BARITEM does + iTextStateID = MPI_HOT; + iBackgroundStateID = MPI_HOT; + + pbrBackground = &g_brItemBackgroundSelected; + } + if ((pUDMI->dis.itemState & ODS_GRAYED) || (pUDMI->dis.itemState & ODS_DISABLED)) { + // disabled / grey text + iTextStateID = MPI_DISABLED; + iBackgroundStateID = MPI_DISABLED; + } + if (pUDMI->dis.itemState & ODS_NOACCEL) { + dwFlags |= DT_HIDEPREFIX; + } + } + + if (!g_menuTheme) { + g_menuTheme = OpenThemeData(hWnd, L"Menu"); + } + + DTTOPTS opts = { sizeof(opts), DTT_TEXTCOLOR | DTT_COMPOSITED | DTT_GLOWSIZE, RGB(0xFF, 0xFF, 0xFF), iTextStateID != MPI_DISABLED ? RGB(0xFF, 0xFF, 0xFF) : RGB(0xC0, 0xC0, 0xC0) }; + //opts.dwFlags = ; + + FillRect(pUDMI->um.hdc, &pUDMI->dis.rcItem, *pbrBackground); + pDrawThemeTextEx(g_menuTheme, pUDMI->um.hdc, MENU_BARITEM, MBI_NORMAL, menuString, mii.cch, dwFlags, &pUDMI->dis.rcItem, &opts); + + return true; + } + case WM_UAHMEASUREMENUITEM: + { + UAHMEASUREMENUITEM* pMmi = (UAHMEASUREMENUITEM*)lParam; + + // allow the default window procedure to handle the message + // since we don't really care about changing the width + *lr = DefWindowProc(hWnd, message, wParam, lParam); + + // but we can modify it here to make it 1/3rd wider for example + //pMmi->mis.itemWidth = (pMmi->mis.itemWidth * 5) / 4; + + return true; + } + case WM_THEMECHANGED: + { + if (g_menuTheme) { + CloseThemeData(g_menuTheme); + g_menuTheme = nullptr; + } + // continue processing in main wndproc + return false; + } + case WM_NCPAINT: + case WM_NCACTIVATE: + *lr = DefWindowProc(hWnd, message, wParam, lParam); + UAHDrawMenuNCBottomLine(hWnd); + return true; + break; + default: + return false; + } +} + diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/UAHMenuBar.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/UAHMenuBar.h new file mode 100644 index 0000000..2420066 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/UAHMenuBar.h @@ -0,0 +1,78 @@ +/*---------------------------------------------------------------------------*/ +// Author : adzm +// Web : https://github.com/adzm/win32-custom-menubar-aero-theme +// License : MIT License +// https://github.com/adzm/win32-custom-menubar-aero-theme/blob/main/LICENSE +/*---------------------------------------------------------------------------*/ + +#pragma once + +// processes messages related to UAH / custom menubar drawing. +// return true if handled, false to continue with normal processing in your wndproc +bool UAHWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT* lr); + +// window messages related to menu bar drawing +#define WM_UAHDESTROYWINDOW 0x0090 // handled by DefWindowProc +#define WM_UAHDRAWMENU 0x0091 // lParam is UAHMENU +#define WM_UAHDRAWMENUITEM 0x0092 // lParam is UAHDRAWMENUITEM +#define WM_UAHINITMENU 0x0093 // handled by DefWindowProc +#define WM_UAHMEASUREMENUITEM 0x0094 // lParam is UAHMEASUREMENUITEM +#define WM_UAHNCPAINTMENUPOPUP 0x0095 // handled by DefWindowProc + +// describes the sizes of the menu bar or menu item +typedef union tagUAHMENUITEMMETRICS +{ + // cx appears to be 14 / 0xE less than rcItem's width! + // cy 0x14 seems stable, i wonder if it is 4 less than rcItem's height which is always 24 atm + struct { + DWORD cx; + DWORD cy; + } rgsizeBar[2]; + struct { + DWORD cx; + DWORD cy; + } rgsizePopup[4]; +} UAHMENUITEMMETRICS; + +// not really used in our case but part of the other structures +typedef struct tagUAHMENUPOPUPMETRICS +{ + DWORD rgcx[4]; + DWORD fUpdateMaxWidths : 2; // from kernel symbols, padded to full dword +} UAHMENUPOPUPMETRICS; + +// hmenu is the main window menu; hdc is the context to draw in +typedef struct tagUAHMENU +{ + HMENU hmenu; + HDC hdc; + DWORD dwFlags; // no idea what these mean, in my testing it's either 0x00000a00 or sometimes 0x00000a10 +} UAHMENU; + +// menu items are always referred to by iPosition here +typedef struct tagUAHMENUITEM +{ + int iPosition; // 0-based position of menu item in menubar + UAHMENUITEMMETRICS umim; + UAHMENUPOPUPMETRICS umpm; +} UAHMENUITEM; + +// the DRAWITEMSTRUCT contains the states of the menu items, as well as +// the position index of the item in the menu, which is duplicated in +// the UAHMENUITEM's iPosition as well +typedef struct UAHDRAWMENUITEM +{ + DRAWITEMSTRUCT dis; // itemID looks uninitialized + UAHMENU um; + UAHMENUITEM umi; +} UAHDRAWMENUITEM; + +// the MEASUREITEMSTRUCT is intended to be filled with the size of the item +// height appears to be ignored, but width can be modified +typedef struct tagUAHMEASUREMENUITEM +{ + MEASUREITEMSTRUCT mis; + UAHMENU um; + UAHMENUITEM umi; +} UAHMEASUREMENUITEM; + diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/UtilityFx.cpp b/CristalDiskMark/source/CrystalDiskMark/Priscilla/UtilityFx.cpp new file mode 100644 index 0000000..e2f0a5d --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/UtilityFx.cpp @@ -0,0 +1,1038 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "UtilityFx.h" + +#include +#pragma comment(lib,"version.lib") + +////------------------------------------------------ +// Debug +////------------------------------------------------ + +static const DWORD DEBUG_MODE_NONE = 0; +static const DWORD DEBUG_MODE_LOG = 1; +static const DWORD DEBUG_MODE_MESSAGE = 2; + +static DWORD debugMode = DEBUG_MODE_NONE; + +void SetDebugMode(DWORD mode) +{ + if (mode <= DEBUG_MODE_MESSAGE) + { + debugMode = mode; + } + else + { + debugMode = DEBUG_MODE_NONE; + } +} + +void DebugPrint(CString cstr) +{ + static BOOL flag = TRUE; + static TCHAR file[MAX_PATH] = _T(""); + static DWORD first = (DWORD)GetTickCountFx(); + CString output; + + output.Format(_T("%08d "), (DWORD)GetTickCountFx() - first); + output += cstr; + output.Append(_T("\n")); + output.Replace(_T("\r"), _T("")); + + if (flag) + { + TCHAR* ptrEnd; + ::GetModuleFileName(NULL, file, MAX_PATH); + if ((ptrEnd = _tcsrchr(file, '.')) != NULL) + { + *ptrEnd = '\0'; +#if _MSC_VER <= 1310 + _tcscat(file, _T(".log")); +#else + _tcscat_s(file, MAX_PATH, _T(".log")); +#endif + } + DeleteFile(file); + flag = FALSE; + } + + if (debugMode == DEBUG_MODE_NONE) + { + return; + } + + FILE* fp = NULL; +#if _MSC_VER <= 1310 + fp = _tfopen(file, _T("ac")); +#else + _tfopen_s(&fp, file, _T("ac")); +#endif + + if (fp != NULL) + { + _ftprintf(fp, _T("%s"), (LPCTSTR)output); + fflush(fp); + fclose(fp); + } + + if (debugMode == DEBUG_MODE_MESSAGE) + { + AfxMessageBox(output); + } +} + +////------------------------------------------------ +// File Information +////------------------------------------------------ + +int GetFileVersion(const TCHAR* file, TCHAR* version) +{ + ULONG reserved = 0; + VS_FIXEDFILEINFO vffi = { 0 }; + TCHAR* buf = NULL; + int Locale = 0; + TCHAR str[256] = { 0 }; + + UINT size = GetFileVersionInfoSize((TCHAR*)file, &reserved); + TCHAR* vbuf = new TCHAR[size]; + if (GetFileVersionInfo((TCHAR*)file, 0, size, vbuf)) + { + VerQueryValue(vbuf, _T("\\"), (void**)&buf, &size); + CopyMemory(&vffi, buf, sizeof(VS_FIXEDFILEINFO)); + + VerQueryValue(vbuf, _T("\\VarFileInfo\\Translation"), (void**)&buf, &size); + CopyMemory(&Locale, buf, sizeof(int)); + wsprintf(str, _T("\\StringFileInfo\\%04X%04X\\%s"), + LOWORD(Locale), HIWORD(Locale), _T("FileVersion")); + VerQueryValue(vbuf, str, (void**)&buf, &size); + +#if _MSC_VER <= 1310 + _tcscpy(str, buf); +#else + _tcscpy_s(str, 256, buf); +#endif + + if (version != NULL) + { +#if _MSC_VER <= 1310 + _tcscpy(version,buf); +#else + _tcscpy_s(version, 256, buf); +#endif + } + } + delete[] vbuf; + + if (_tcscmp(str, _T("")) != 0) + { + return int(_tstof(str) * 100); + } + else + { + return 0; + } +} + +void GetFileVersionEx(const TCHAR* file, CString& version) +{ + ULONG reserved = 0; + VS_FIXEDFILEINFO vffi = { 0 }; + TCHAR* buf = NULL; + int Locale = 0; + TCHAR str[256] = { 0 }; + + UINT size = GetFileVersionInfoSize((TCHAR*)file, &reserved); + TCHAR* vbuf = new TCHAR[size]; + if (GetFileVersionInfo((TCHAR*)file, 0, size, vbuf)) + { + VerQueryValue(vbuf, _T("\\"), (void**)&buf, &size); + CopyMemory(&vffi, buf, sizeof(VS_FIXEDFILEINFO)); + + if (vffi.dwSignature == 0xfeef04bd) + { + version.Format(_T("%d.%d.%d.%d"), + HIWORD(vffi.dwFileVersionMS), + LOWORD(vffi.dwFileVersionMS), + HIWORD(vffi.dwFileVersionLS), + LOWORD(vffi.dwFileVersionLS)); + } + } + delete[] vbuf; +} + +BOOL IsFileExist(const TCHAR* path) +{ + FILE* fp = NULL; + +#if _MSC_VER <= 1310 + fp = _tfopen(path, _T("rb")); + if (fp == NULL) +#else + errno_t err = 0; + err = _tfopen_s(&fp, path, _T("rb")); + if (err != 0 || fp == NULL) +#endif + { + return FALSE; + } + fclose(fp); + return TRUE; +} + +BOOL CanWriteFile(const TCHAR* path) +{ + HANDLE hFile = CreateFile(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL); + + if (hFile == INVALID_HANDLE_VALUE) { return FALSE; } + const char* testData = "1"; + DWORD bytesWritten; + BOOL writeResult = WriteFile(hFile, testData, (DWORD)strlen(testData), &bytesWritten, NULL); + CloseHandle(hFile); + + return writeResult; +} + + +////------------------------------------------------ +// Utility +////------------------------------------------------ + +typedef ULONGLONG(WINAPI* FuncGetTickCount64)(); + +ULONGLONG GetTickCountFx() +{ + HMODULE hModule = GetModuleHandle(_T("kernel32.dll")); + FuncGetTickCount64 pGetTickCount64 = NULL; + if (hModule) + { + pGetTickCount64 = (FuncGetTickCount64)GetProcAddress(hModule, "GetTickCount64"); + } + + if (pGetTickCount64) + { + return (ULONGLONG)pGetTickCount64(); + } + else + { +#if _MSC_VER <= 1310 + return (ULONGLONG)GetTickCount(); +#else + return _Pragma("warning(suppress: 28159)") (ULONGLONG)GetTickCount(); +#endif + } +} + +ULONG64 B8toB64(BYTE b0, BYTE b1, BYTE b2, BYTE b3, BYTE b4, BYTE b5, BYTE b6, BYTE b7) +{ + ULONG64 data = + ((ULONG64)b7 << 56) + + ((ULONG64)b6 << 48) + + ((ULONG64)b5 << 40) + + ((ULONG64)b4 << 32) + + ((ULONG64)b3 << 24) + + ((ULONG64)b2 << 16) + + ((ULONG64)b1 << 8) + + ((ULONG64)b0 << 0); + + return data; +} + +DWORD B8toB32(BYTE b0, BYTE b1, BYTE b2, BYTE b3) +{ + DWORD data = + ((DWORD)b3 << 24) + + ((DWORD)b2 << 16) + + ((DWORD)b1 << 8) + + ((DWORD)b0 << 0); + + return data; +} + +void SplitCString(const CString& str, const CString& delimiter, CStringArray& arr) +{ + int startPos = 0; + while (startPos >= 0) + { + int endPos = str.Find(delimiter, startPos); + if (endPos == -1) + { + arr.Add(str.Mid(startPos)); + break; + } + arr.Add(str.Mid(startPos, endPos - startPos)); + startPos = endPos + lstrlen(delimiter); + } +} + + + +#if _MSC_VER > 1310 +// --------------------------------------------------------- +// 20260123: Safe for unaligned/page-boundary (Using memcpy for atomic-like MOV) >>> + +// Environment Validation (Compile-time) > +static_assert(sizeof(USHORT) == 2, "! (sizeof(USHORT) == 2)"); +static_assert(sizeof(INT) == 4, "! (sizeof(INT) == 4)"); +static_assert(sizeof(DWORD) == 4, "! (sizeof(DWORD) == 4)"); +static_assert(sizeof(ULONG64) == 8, "! (sizeof(ULONG64) == 8)"); + +// Boundary-Safe Loaders > + +NODISCARD ULONG64 B8toB64le_ptr(_In_reads_(8) const BYTE* v) noexcept { + ULONG64 u64; + ::memcpy(&u64, v, sizeof(u64)); + return u64; +} +NODISCARD DWORD B8toB32le_ptr(_In_reads_(4) const BYTE* v) noexcept { + return ((static_cast(v[0])) + | (static_cast(v[1]) << 8) + | (static_cast(v[2]) << 16) + | (static_cast(v[3]) << 24)); +} +NODISCARD INT B8toINTle_ptr(_In_reads_(4) const BYTE* v) noexcept { + return static_cast((static_cast(v[0])) + | (static_cast(v[1]) << 8) + | (static_cast(v[2]) << 16) + | (static_cast(v[3]) << 24)); +} +NODISCARD USHORT B8toB16le_ptr(_In_reads_(2) const BYTE* v) noexcept { + return static_cast((static_cast(v[0])) + | (static_cast(v[1]) << 8)); +} +NODISCARD SHORT B8toSHORTle_ptr(_In_reads_(2) const BYTE* v) noexcept { + return static_cast((static_cast(v[0])) + | (static_cast(v[1]) << 8)); +} +NODISCARD ULONG64 B8toB64le(const BYTE(&v)[6]) noexcept { + return ((static_cast(v[0])) + | (static_cast(v[1]) << 8) + | (static_cast(v[2]) << 16) + | (static_cast(v[3]) << 24) + | (static_cast(v[4]) << 32) + | (static_cast(v[5]) << 40)); +} +NODISCARD DWORD B8toB32le(const BYTE(&v)[6]) noexcept { + return ((static_cast(v[0])) + | (static_cast(v[1]) << 8) + | (static_cast(v[2]) << 16) + | (static_cast(v[3]) << 24)); +} +NODISCARD INT B8toINTle(const BYTE(&v)[6]) noexcept { + return static_cast((static_cast(v[0])) + | (static_cast(v[1]) << 8) + | (static_cast(v[2]) << 16) + | (static_cast(v[3]) << 24)); +} +NODISCARD USHORT B8toB16le(const BYTE(&v)[6]) noexcept { + return ((static_cast(v[0])) + | (static_cast(v[1]) << 8)); +} + +// 20260123: Safe for unaligned/page-boundary <<< +// --------------------------------------------------------- +#endif + + +////------------------------------------------------ +// .ini support function +////------------------------------------------------ + +DWORD GetPrivateProfileStringFx(LPCTSTR lpAppName, LPCTSTR lpKeyName, LPCTSTR lpDefault, LPTSTR lpReturnedString, DWORD nSize, LPCTSTR lpFileName) +{ + DWORD result = 0; + CString key = lpKeyName; + key.Replace(_T("="), _T("%#3D")); + key.Replace(_T("\""), _T("%#22")); + result = GetPrivateProfileString(lpAppName, key, lpDefault, lpReturnedString, nSize, lpFileName); + + CString value = lpReturnedString; + value.Replace(_T("%#3D"), _T("=")); + value.Replace(_T("%#22"), _T("\"")); + +#if _MSC_VER <= 1310 + _tcscpy(lpReturnedString, (LPCTSTR)value); +#else + _tcscpy_s(lpReturnedString, nSize, (LPCTSTR)value); +#endif + + return result; +} + +UINT GetPrivateProfileIntFx(LPCTSTR lpAppName, LPCTSTR lpKeyName, INT nDefault, LPCTSTR lpFileName) +{ + CString key = lpKeyName; + key.Replace(_T("="), _T("%#3D")); + key.Replace(_T("\""), _T("%#22")); + + return GetPrivateProfileInt(lpAppName, key, nDefault, lpFileName); +} + +BOOL WritePrivateProfileStringFx(LPCTSTR lpAppName, LPCTSTR lpKeyName, LPCTSTR lpString, LPCTSTR lpFileName) +{ + CString key = lpKeyName; + key.Replace(_T("="), _T("%#3D")); + key.Replace(_T("\""), _T("%#22")); + + CString value = lpString; + value.Replace(_T("="), _T("%#3D")); + value.Replace(_T("\""), _T("%#22")); + + value = _T("\"" + value + _T("\"")); + + return WritePrivateProfileString(lpAppName, key, value, lpFileName); +} + +////------------------------------------------------ +// Check CodeSign +////------------------------------------------------ +#if _MSC_VER > 1310 +#include +#pragma comment(lib, "Wintrust.lib") +#pragma comment(lib, "Crypt32.lib") + +BOOL CheckCodeSign(LPCWSTR certName, LPCWSTR filePath) +{ +#ifdef _DEBUG + return TRUE; +#endif + + // check sign + + WINTRUST_FILE_INFO FileData = { sizeof(WINTRUST_FILE_INFO) }; + FileData.pcwszFilePath = filePath; + + GUID WVTPolicyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2; + WINTRUST_DATA WinTrustData = { sizeof(WinTrustData) }; + WinTrustData.dwUIChoice = WTD_UI_NONE; + WinTrustData.fdwRevocationChecks = WTD_REVOKE_NONE; + WinTrustData.dwUnionChoice = WTD_CHOICE_FILE; + WinTrustData.dwStateAction = WTD_STATEACTION_VERIFY; + WinTrustData.pFile = &FileData; + + const LONG ret = WinVerifyTrust(NULL, &WVTPolicyGUID, &WinTrustData); + + bool cert_chk = false; + + if (ret == ERROR_SUCCESS) { + // retreive the signer certificate and display its information + CRYPT_PROVIDER_DATA const* psProvData = NULL; + CRYPT_PROVIDER_SGNR* psProvSigner = NULL; + CRYPT_PROVIDER_CERT* psProvCert = NULL; + + psProvData = WTHelperProvDataFromStateData(WinTrustData.hWVTStateData); + if (psProvData) { + psProvSigner = WTHelperGetProvSignerFromChain((PCRYPT_PROVIDER_DATA)psProvData, 0, FALSE, 0); + if (psProvSigner) { + psProvCert = WTHelperGetProvCertFromChain(psProvSigner, 0); + if (psProvCert) { + wchar_t szCertName[200] = {0}; + DWORD dwStrType = CERT_X500_NAME_STR; + CertGetNameStringW(psProvCert->pCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, &dwStrType, szCertName, 200); + cert_chk = !(szCertName[0] == '\0' || wcscmp(szCertName, certName) != 0); + } + } + } + } + WinTrustData.dwStateAction = WTD_STATEACTION_CLOSE; + (void)WinVerifyTrust(NULL, &WVTPolicyGUID, &WinTrustData); + if (!cert_chk) return FALSE; + + return TRUE; +} +#endif + +////------------------------------------------------ +// Play Sound +////------------------------------------------------ + +#include +#include "digitalv.h" + +#pragma comment(lib, "winmm.lib") + +/* +void ShowMciError(DWORD error) +{ + TCHAR errorBuf[MAXERRORLENGTH]; + + if (mciGetErrorString(error, (LPTSTR)errorBuf, MAXERRORLENGTH)) + { + AfxMessageBox(errorBuf); + } + else + { + AfxMessageBox(_T("Unknown Error"); + } +} +*/ + +BOOL AlertSound(const CString& alertSoundPath, int volume) +{ +#if _MSC_VER <= 1310 + PlaySound(alertSoundPath, NULL, SND_SYNC); +#else + + static MCI_OPEN_PARMS mop = { 0 }; + static MCI_PLAY_PARMS mpp = { 0 }; + static MCI_GENERIC_PARMS mgp = { 0 }; + MCIERROR error = 0; + + if (mop.wDeviceID != 0) + { + // Stop and close + error = mciSendCommandW(mop.wDeviceID, MCI_STOP, 0, 0); + error = mciSendCommandW(mop.wDeviceID, MCI_CLOSE, 0, 0); + mop.wDeviceID = 0; + ZeroMemory(&mop, sizeof(MCI_OPEN_PARMS)); + if (error) + { + // ShowMciError(error); + return FALSE; + } + } + + if (alertSoundPath.Compare(_T("")) == 0) { + // Close + error = mciSendCommandW(mop.wDeviceID, MCI_CLOSE, 0, 0); + if (error) + { + // ShowMciError(error); + return FALSE; + } + return TRUE; + } + + // Open + mop.lpstrElementName = (LPCTSTR)alertSoundPath; + mop.lpstrDeviceType = _T("MPEGVideo"); + error = mciSendCommandW(NULL, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_ELEMENT, (DWORD_PTR)(&mop)); + + if (error) + { + // ShowMciError(error); + return FALSE; + } + + // Set volume + if (volume < 0 || volume > 100) { volume = 80; } + + MCI_DGV_SETAUDIO_PARMS parms = { 0 }; + parms.dwItem = MCI_DGV_SETAUDIO_VOLUME; + parms.dwValue = volume * 10; // 0-1000 + error = mciSendCommand(mop.wDeviceID, MCI_SETAUDIO, MCI_DGV_SETAUDIO_ITEM | MCI_DGV_SETAUDIO_VALUE, (DWORD_PTR)&parms); + if (error) + { + // ShowMciError(error); + return FALSE; + } + // Seek + error = mciSendCommand(mop.wDeviceID, MCI_SEEK, MCI_SEEK_TO_START, reinterpret_cast(&mgp)); + if (error) + { + // ShowMciError(error); + return FALSE; + } + + // Play + error = mciSendCommandW(mop.wDeviceID, MCI_PLAY, 0, reinterpret_cast(&mpp)); + if (error) + { + // ShowMciError(error); + return FALSE; + } +#endif + + return TRUE; +} + + +////------------------------------------------------ +// Hash +////------------------------------------------------ + +#if _MSC_VER > 1310 +#include +#include +#include +#include +#include +CStringA MD5(const CStringA& str) +{ + HCRYPTPROV hProv = 0; + HCRYPTHASH hHash = 0; + BYTE hash[16]; + DWORD hashLen = 16; + CStringA hashStr; + + if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + return ""; + } + + if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) { + CryptReleaseContext(hProv, 0); + return ""; + } + + std::string utf8Str(str); + + if (!CryptHashData(hHash, reinterpret_cast(utf8Str.c_str()), (DWORD)utf8Str.size(), 0)) { + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + return ""; + } + + if (!CryptGetHashParam(hHash, HP_HASHVAL, hash, &hashLen, 0)) { + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + return ""; + } + + for (DWORD i = 0; i < hashLen; ++i) { + CString temp; + temp.Format(_T("%02x"), hash[i]); + hashStr += temp; + } + + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + + return hashStr; +} +#else +#include "md5.h" +CStringA MD5(const CStringA& str) +{ + char* input = (char*)(LPCSTR)str; + uint8_t result[16] = { 0 }; + md5String(input, result); + + CStringA hashStr; + for (int i = 0; i < 16; ++i) + { + CStringA byteStr; + byteStr.Format("%02x", result[i]); + hashStr += byteStr; + } + + return hashStr; +} +#endif + +////------------------------------------------------ +// Character Converter +////------------------------------------------------ + +CStringA URLEncode(const CStringA& str) +{ + CStringA encoded; + for (int i = 0; i < str.GetLength(); i++) + { + if (isalnum((BYTE)str[i]) || str[i] == '-' || str[i] == '_' || str[i] == '.' || str[i] == '~') + { + encoded += str[i]; + } + else + { + encoded.AppendFormat("%%%02X", (BYTE)str[i]); + } + } + return encoded; +} + +#if _MSC_VER <= 1310 + +// https://github.com/roytam1/RetroZilla/commit/e44dd98dbf2effeff3305430835e889d76ec73b4#diff-065242b491de972d8519dbbae0c4c8dfb6a1a3c3eb8b9936d56a4c1ad63ec7abR126-R282 +/*** UTF16<-->UTF8 functions minicking MultiByteToWideChar/WideCharToMultiByte ***/ +int utf8GetMaskIndex(unsigned char n) { + if ((unsigned char)(n + 2) < 0xc2) return 1; // 00~10111111, fe, ff + if (n < 0xe0) return 2; // 110xxxxx + if (n < 0xf0) return 3; // 1110xxxx + if (n < 0xf8) return 4; // 11110xxx + if (n < 0xfc) return 5; // 111110xx + return 6; // 1111110x +} + +int wc2Utf8Len(wchar_t** n, int* len) { + wchar_t* ch = *n, ch2; + int qch; + if ((0xD800 <= *ch && *ch <= 0xDBFF) && *len) { + ch2 = *(ch + 1); + if (0xDC00 <= ch2 && ch2 <= 0xDFFF) { + qch = 0x10000 + (((*ch - 0xD800) & 0x3ff) << 10) + ((ch2 - 0xDC00) & 0x3ff); + (*n)++; + (*len)--; + } + } + else + qch = (int)*ch; + + if (qch <= 0x7f) return 1; + else if (qch <= 0x7ff) return 2; + else if (qch <= 0xffff) return 3; + else if (qch <= 0x1fffff) return 4; + else if (qch <= 0x3ffffff) return 5; + else return 6; +} + +int Utf8ToWideChar(unsigned int unused1, unsigned long unused2, char* sb, int ss, wchar_t* wb, int ws) { + static const unsigned char utf8mask[] = { 0, 0xff, 0x1f, 0x0f, 0x07, 0x03, 0x01 }; + char* p = (char*)(sb); + char* e = (char*)(sb + ss); + wchar_t* w = wb; + int cnt = 0, t, qch; + + if (ss < 1) { + ss = lstrlenA(sb); + e = (char*)(sb + ss); + } + + if (wb && ws) { + for (; p < e; ++w) { + t = utf8GetMaskIndex(*p); + qch = (*p++ & utf8mask[t]); + while (p < e && --t) + qch <<= 6, qch |= (*p++) & 0x3f; + if (qch < 0x10000) { + if (cnt <= ws) + *w = (wchar_t)qch; + cnt++; + } + else { + if (cnt + 2 <= ws) { + *w++ = (wchar_t)(0xD800 + (((qch - 0x10000) >> 10) & 0x3ff)), + *w = (wchar_t)(0xDC00 + (((qch - 0x10000)) & 0x3ff)); + } + cnt += 2; + } + } + if (cnt < ws) { + *(wb + cnt) = 0; + return cnt; + } + else { + *(wb + ws) = 0; + return ws; + } + } + else { + for (t; p < e;) { + t = utf8GetMaskIndex(*p); + qch = (*p++ & utf8mask[t]); + while (p < e && --t) + qch <<= 6, qch |= (*p++) & 0x3f; + if (qch < 0x10000) + cnt++; + else + cnt += 2; + } + return cnt + 1; + } +} + +int WideCharToUtf8(unsigned int unused1, unsigned long unused2, wchar_t* wb, int ws, char* sb, int ss) { + wchar_t* p = (wchar_t*)(wb); + wchar_t* e = (wchar_t*)(wb + ws); + wchar_t* oldp; + char* s = sb; + int cnt = 0, qch, t; + + if (ws < 1) { + ws = lstrlenW(wb); + e = (wchar_t*)(wb + ws); + } + + if (sb && ss) { + for (t; p < e; ++p) { + oldp = p; + t = wc2Utf8Len(&p, &ws); + + if (p != oldp) { /* unicode surrogates encountered */ + qch = 0x10000 + (((*oldp - 0xD800) & 0x3ff) << 10) + ((*p - 0xDC00) & 0x3ff); + } + else + qch = *p; + + if (qch <= 0x7f) + *s++ = (char)(qch), + cnt++; + else if (qch <= 0x7ff) + *s++ = 0xc0 | (char)(qch >> 6), + *s++ = 0x80 | (char)(qch & 0x3f), + cnt += 2; + else if (qch <= 0xffff) + *s++ = 0xe0 | (char)(qch >> 12), + *s++ = 0x80 | (char)((qch >> 6) & 0x3f), + *s++ = 0x80 | (char)(qch & 0x3f), + cnt += 3; + else if (qch <= 0x1fffff) + *s++ = 0xf0 | (char)(qch >> 18), + *s++ = 0x80 | (char)((qch >> 12) & 0x3f), + *s++ = 0x80 | (char)((qch >> 6) & 0x3f), + *s++ = 0x80 | (char)(qch & 0x3f), + cnt += 4; + else if (qch <= 0x3ffffff) + *s++ = 0xf8 | (char)(qch >> 24), + *s++ = 0x80 | (char)((qch >> 18) & 0x3f), + *s++ = 0x80 | (char)((qch >> 12) & 0x3f), + *s++ = 0x80 | (char)((qch >> 6) & 0x3f), + *s++ = 0x80 | (char)(qch & 0x3f), + cnt += 5; + else + *s++ = 0xfc | (char)(qch >> 30), + *s++ = 0x80 | (char)((qch >> 24) & 0x3f), + *s++ = 0x80 | (char)((qch >> 18) & 0x3f), + *s++ = 0x80 | (char)((qch >> 12) & 0x3f), + *s++ = 0x80 | (char)((qch >> 6) & 0x3f), + *s++ = 0x80 | (char)(qch & 0x3f), + cnt += 6; + } + if (cnt < ss) { + *(sb + cnt) = 0; + return cnt; + } + else { + *(sb + ss) = 0; + return ss; + } + } + else { + for (t; p < e; ++p) { + t = wc2Utf8Len(&p, &ws); + cnt += t; + } + return cnt + 1; + } +} +/*** Ends ***/ + +#ifdef UNICODE +CStringA UTF16toUTF8(const CStringW& utf16str) +{ + if (IsNT3() || IsNT4()) + { + CStringA utf8str; + WCHAR* utf16string = new WCHAR[(utf16str.GetLength() + 1) * 2]; + wsprintf(utf16string, L"%s", utf16str); + int utf16Length = utf16str.GetLength(); + int utf8Length = WideCharToUtf8(CP_UTF8, 0, utf16string, -1, NULL, 0); + if (utf8Length <= 0) { return ""; } + WideCharToUtf8(CP_UTF8, 0, utf16string, -1, utf8str.GetBuffer(utf8Length), utf8Length); + utf8str.ReleaseBuffer(); + delete utf16string; + return utf8str; + } + else + { + CStringA utf8str; + int utf8Length = WideCharToMultiByte(CP_UTF8, 0, utf16str, -1, NULL, 0, NULL, NULL); + if (utf8Length <= 0) { return ""; } + WideCharToMultiByte(CP_UTF8, 0, utf16str, -1, utf8str.GetBuffer(utf8Length), utf8Length, NULL, NULL); + utf8str.ReleaseBuffer(); + return utf8str; + } +} + +CStringW UTF8toUTF16(const CStringA& utf8str) +{ + CStringW utf16str; + CHAR* utf8string = new CHAR[(utf8str.GetLength() + 1) * 2]; + sprintf(utf8string, "%s", utf8str); + int utf8Length = utf8str.GetLength(); + int utf16Length = Utf8ToWideChar(1200, 0, utf8string, -1, NULL, 0); + if (utf16Length <= 0) { return L""; } + Utf8ToWideChar(1200, 0, utf8string, -1, utf16str.GetBuffer(utf16Length), utf16Length); + utf16str.ReleaseBuffer(); + delete utf8string; + return utf16str; +} + +#else +CStringA ANSItoUTF8(const CStringA& ansiStr) +{ + int utf16Length = MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, NULL, 0); + if (utf16Length <= 0) { return ""; } + CStringW utf16str; + MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, utf16str.GetBuffer(utf16Length), utf16Length); + utf16str.ReleaseBuffer(); + + CStringA utf8str; + int utf8Length = WideCharToUtf8(CP_UTF8, 0, utf16str.GetBuffer(utf16Length), -1, NULL, 0); + if (utf8Length <= 0) { return ""; } + WideCharToUtf8(CP_UTF8, 0, utf16str.GetBuffer(utf16Length), -1, utf8str.GetBuffer(utf8Length), utf8Length); + utf8str.ReleaseBuffer(); + return utf8str; +} +#endif +#else + +CStringA UTF16toUTF8(const CStringW& utf16str) +{ + CStringA utf8str(CW2A(utf16str, CP_UTF8)); + return utf8str; +} + +CStringW UTF8toUTF16(const CStringA& utf8str) +{ + CStringW utf16str; + utf16str = CA2W(utf8str); + return utf16str; +} +#endif + +#ifdef UNICODE +CStringA UE(const CStringW& utf16str) +{ + return URLEncode(UTF16toUTF8(utf16str)); +} +#else +CStringA UE(const CStringA& ansiStr) +{ + return URLEncode(ANSItoUTF8(ansiStr)); +} +#endif +////------------------------------------------------ +// Clipboard +////------------------------------------------------ + +void SetClipboardText(CString clip) +{ + if (OpenClipboard(NULL)) + { + HGLOBAL clipbuffer; + TCHAR* buffer = NULL; + EmptyClipboard(); + clipbuffer = GlobalAlloc(GMEM_DDESHARE, sizeof(TCHAR) * (clip.GetLength() + 1)); + if (clipbuffer != NULL) + { + buffer = (TCHAR*)GlobalLock(clipbuffer); + if (buffer != NULL) + { +#if _MSC_VER <= 1310 + _tcscpy(buffer, LPCTSTR(clip)); +#else + _tcscpy_s(buffer, clip.GetLength() + 1, LPCTSTR(clip)); +#endif + } + GlobalUnlock(clipbuffer); +#ifdef UNICODE + SetClipboardData(CF_UNICODETEXT, clipbuffer); +#else + SetClipboardData(CF_TEXT, clipbuffer); +#endif + } + CloseClipboard(); + } +} + +////------------------------------------------------ +// SHLWAPI.DLL compatible functions +////------------------------------------------------ +#if _MSC_VER <= 1310 +BOOL PathRemoveFileSpecFxA(char* path) +{ + char* filespec = path; + BOOL modified = FALSE; + + if (!path || !*path) { return FALSE; } + if (*path == '\\') { filespec = ++path; } + if (*path == '\\') { filespec = ++path; } + + while (*path) + { + if (*path == '\\') + { + filespec = path; + } + else if (*path == ':') + { + filespec = ++path; + if (*path == '\\') { filespec++; } + } + path = CharNextA(path); + } + + if (*filespec) + { + *filespec = '\0'; + modified = TRUE; + } + + return modified; +} + +BOOL PathRemoveFileSpecFxW(WCHAR* path) +{ + WCHAR* filespec = path; + BOOL modified = FALSE; + + if (!path || !*path) { return FALSE; } + if (*path == '\\') { filespec = ++path; } + if (*path == '\\') { filespec = ++path; } + + while (*path) + { + if (*path == '\\') + { + filespec = path; + } + else if (*path == ':') + { + filespec = ++path; + if (*path == '\\') { filespec++; } + } + path++; + } + + if (*filespec) + { + *filespec = '\0'; + modified = TRUE; + } + + return modified; +} + +char* PathFindFileNameFxA(const char* path) +{ + const char* p = path; + const char* name = NULL; + while (*p) + { + if (*p == '\\' || *p == '/') + { + name = p + 1; + } + p = CharNextA(p); + } + if (name) + { + return (char*)name; + } + return (char*)path; +} + +WCHAR* PathFindFileNameFxW(const WCHAR* path) +{ + const WCHAR* p = path; + const WCHAR* name = NULL; + while (*p) + { + if (*p == '\\' || *p == '/') + { + name = p + 1; + } + p++; + } + if (name) + { + return (WCHAR*)name; + } + return (WCHAR*)path; +} +#endif \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/UtilityFx.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/UtilityFx.h new file mode 100644 index 0000000..cc9bf07 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/UtilityFx.h @@ -0,0 +1,143 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once + +////------------------------------------------------ +// Debug +////------------------------------------------------ + +void SetDebugMode(DWORD mode); +void DebugPrint(CString cstr); + +////------------------------------------------------ +// File Information +////------------------------------------------------ + +int GetFileVersion(const TCHAR* fileName, TCHAR* version = NULL); +void GetFileVersionEx(const TCHAR* file, CString& version); +BOOL IsFileExist(const TCHAR* fileName); +BOOL CanWriteFile(const TCHAR* fileName); + +////------------------------------------------------ +// Utility +////------------------------------------------------ + +ULONGLONG GetTickCountFx(); + +ULONG64 B8toB64(BYTE b0, BYTE b1 = 0, BYTE b2 = 0, BYTE b3 = 0, BYTE b4 = 0, BYTE b5 = 0, BYTE b6 = 0, BYTE b7 = 0); +DWORD B8toB32(BYTE b0, BYTE b1 = 0, BYTE b2 = 0, BYTE b3 = 0); +void SplitCString(const CString& str, const CString& delimiter, CStringArray& arr); + +#if _MSC_VER > 1310 +// --------------------------------------------------------- +// 20260123: Safe for unaligned/page-boundary (Using memcpy for atomic-like MOV) >>> +#ifndef NODISCARD + #if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) + #define NODISCARD [[nodiscard]] + #elif defined(_MSC_VER) && (_MSC_VER >= 1700) + #define NODISCARD _Check_return_ + #else + #define NODISCARD + #endif +#endif + +// SAL annotation support +#ifndef _In_reads_ + #ifdef _MSC_VER + #include + #endif + //without SAL + #ifndef _In_reads_ + #define _In_reads_(s) + #endif +#endif + +/* 8byte(le) to ULONG64 (Safe for unaligned/page-boundary) */ +NODISCARD ULONG64 B8toB64le_ptr(_In_reads_(8) const BYTE* v) noexcept; +/* 4byte(le) to DWORD (Safe for unaligned/page-boundary) */ +NODISCARD DWORD B8toB32le_ptr(_In_reads_(4) const BYTE* v) noexcept; +/* 4byte(le) to INT (Controlled sign extension) */ +NODISCARD INT B8toINTle_ptr(_In_reads_(4) const BYTE* v) noexcept; +/* 2byte(le) to USHORT (Safe for unaligned/page-boundary) */ +NODISCARD USHORT B8toB16le_ptr(_In_reads_(2) const BYTE* v) noexcept; +/* 2byte(le) to signed SHORT (Controlled sign extension) */ +NODISCARD SHORT B8toSHORTle_ptr(_In_reads_(2) const BYTE* v) noexcept; +/* 6byte(le) to ULONG64 (Safe for page-boundary) */ +NODISCARD ULONG64 B8toB64le(const BYTE(&v)[6]) noexcept; +/* 6byte(le) to DWORD (Safe for page-boundary) */ +NODISCARD DWORD B8toB32le(const BYTE(&v)[6]) noexcept; +/* 6byte(le) to INT (Controlled sign extension) */ +NODISCARD INT B8toINTle(const BYTE(&v)[6]) noexcept; +/* 6byte(le) to USHORT (Safe for page-boundary) */ +NODISCARD USHORT B8toB16le(const BYTE(&v)[6]) noexcept; + +// 20260123: Safe for unaligned/page-boundary <<< +// --------------------------------------------------------- +#endif + +////------------------------------------------------ +// .ini support function +////------------------------------------------------ + +DWORD GetPrivateProfileStringFx(LPCTSTR lpAppName, LPCTSTR lpKeyName, LPCTSTR lpDefault, LPTSTR lpReturnedString,DWORD nSize,LPCTSTR lpFileName); +UINT GetPrivateProfileIntFx(LPCTSTR lpAppName, LPCTSTR lpKeyName, INT nDefault, LPCTSTR lpFileName); +BOOL WritePrivateProfileStringFx(LPCTSTR lpAppName, LPCTSTR lpKeyName, LPCTSTR lpString, LPCTSTR lpFileName); + +////------------------------------------------------ +// Check CodeSign +////------------------------------------------------ + +#if _MSC_VER > 1310 +BOOL CheckCodeSign(LPCWSTR certName, LPCWSTR filePath); +#endif + +////------------------------------------------------ +// Play Sound +////------------------------------------------------ + +BOOL AlertSound(const CString& alertSoundPath, int volume); + +////------------------------------------------------ +// Hash +////------------------------------------------------ + +CStringA MD5(const CStringA& str); + +////------------------------------------------------ +// Character Converter +////------------------------------------------------ + +CStringW UTF8toUTF16(const CStringA& utf8str); +CStringA UTF16toUTF8(const CStringW& utf16str); +CStringA URLEncode(const CStringA& str); +CStringA UE(const CStringW& utf16str); +CStringA UE(const CStringA& ansiStr); + +////------------------------------------------------ +// Clipboard +////------------------------------------------------ + +void SetClipboardText(CString clip); + +////------------------------------------------------ +// SHLWAPI.DLL compatible functions +////------------------------------------------------ +#if _MSC_VER <= 1310 +#ifdef UNICODE +#define PathRemoveFileSpecFx PathRemoveFileSpecFxW +#define PathFindFileNameFx PathFindFileNameFxW +#else +#define PathRemoveFileSpecFx PathRemoveFileSpecFxA +#define PathFindFileNameFx PathFindFileNameFxA +#endif + +BOOL PathRemoveFileSpecFxA(char* path); +BOOL PathRemoveFileSpecFxW(WCHAR* path); +char* PathFindFileNameFxA(const char* path); +WCHAR* PathFindFileNameFxW(const WCHAR* path); +#endif diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/md5.cpp b/CristalDiskMark/source/CrystalDiskMark/Priscilla/md5.cpp new file mode 100644 index 0000000..645a075 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/md5.cpp @@ -0,0 +1,226 @@ +/* + * Derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm + * and modified slightly to be functionally identical but condensed into control structures. + */ + +#include "stdafx.h" +#include "md5.h" + +/* + * Constants defined by the MD5 algorithm + */ +#define A 0x67452301 +#define B 0xefcdab89 +#define C 0x98badcfe +#define D 0x10325476 + +static uint32_t S[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; + +static uint32_t K[] = {0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391}; + +/* + * Padding used to make the size (in bits) of the input congruent to 448 mod 512 + */ +static uint8_t PADDING[] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +/* + * Bit-manipulation functions defined by the MD5 algorithm + */ +#define F(X, Y, Z) ((X & Y) | (~X & Z)) +#define G(X, Y, Z) ((X & Z) | (Y & ~Z)) +#define H(X, Y, Z) (X ^ Y ^ Z) +#define I(X, Y, Z) (Y ^ (X | ~Z)) + +/* + * Rotates a 32-bit word left by n bits + */ +uint32_t rotateLeft(uint32_t x, uint32_t n){ + return (x << n) | (x >> (32 - n)); +} + + +/* + * Initialize a context + */ +void md5Init(MD5Context *ctx){ + ctx->size = (uint64_t)0; + + ctx->buffer[0] = (uint32_t)A; + ctx->buffer[1] = (uint32_t)B; + ctx->buffer[2] = (uint32_t)C; + ctx->buffer[3] = (uint32_t)D; +} + +/* + * Add some amount of input to the context + * + * If the input fills out a block of 512 bits, apply the algorithm (md5Step) + * and save the result in the buffer. Also updates the overall size. + */ +void md5Update(MD5Context *ctx, uint8_t *input_buffer, size_t input_len){ + uint32_t input[16]; + unsigned int offset = (unsigned int)(ctx->size % 64); + ctx->size += (uint64_t)input_len; + + // Copy each byte in input_buffer into the next space in our context input + for(unsigned int i = 0; i < input_len; ++i){ + ctx->input[offset++] = (uint8_t)*(input_buffer + i); + + // If we've filled our context input, copy it into our local array input + // then reset the offset to 0 and fill in a new buffer. + // Every time we fill out a chunk, we run it through the algorithm + // to enable some back and forth between cpu and i/o + if(offset % 64 == 0){ + for(unsigned int j = 0; j < 16; ++j){ + // Convert to little-endian + // The local variable `input` our 512-bit chunk separated into 32-bit words + // we can use in calculations + input[j] = (uint32_t)(ctx->input[(j * 4) + 3]) << 24 | + (uint32_t)(ctx->input[(j * 4) + 2]) << 16 | + (uint32_t)(ctx->input[(j * 4) + 1]) << 8 | + (uint32_t)(ctx->input[(j * 4)]); + } + md5Step(ctx->buffer, input); + offset = 0; + } + } +} + +/* + * Pad the current input to get to 448 bytes, append the size in bits to the very end, + * and save the result of the final iteration into digest. + */ +void md5Finalize(MD5Context *ctx){ + uint32_t input[16]; + unsigned int offset = (unsigned int)(ctx->size % 64); + unsigned int padding_length = offset < 56 ? 56 - offset : (56 + 64) - offset; + + // Fill in the padding and undo the changes to size that resulted from the update + md5Update(ctx, PADDING, padding_length); + ctx->size -= (uint64_t)padding_length; + + // Do a final update (internal to this function) + // Last two 32-bit words are the two halves of the size (converted from bytes to bits) + for(unsigned int j = 0; j < 14; ++j){ + input[j] = (uint32_t)(ctx->input[(j * 4) + 3]) << 24 | + (uint32_t)(ctx->input[(j * 4) + 2]) << 16 | + (uint32_t)(ctx->input[(j * 4) + 1]) << 8 | + (uint32_t)(ctx->input[(j * 4)]); + } + input[14] = (uint32_t)(ctx->size * 8); + input[15] = (uint32_t)((ctx->size * 8) >> 32); + + md5Step(ctx->buffer, input); + + // Move the result into digest (convert from little-endian) + for(unsigned int i = 0; i < 4; ++i){ + ctx->digest[(i * 4) + 0] = (uint8_t)((ctx->buffer[i] & 0x000000FF)); + ctx->digest[(i * 4) + 1] = (uint8_t)((ctx->buffer[i] & 0x0000FF00) >> 8); + ctx->digest[(i * 4) + 2] = (uint8_t)((ctx->buffer[i] & 0x00FF0000) >> 16); + ctx->digest[(i * 4) + 3] = (uint8_t)((ctx->buffer[i] & 0xFF000000) >> 24); + } +} + +/* + * Step on 512 bits of input with the main MD5 algorithm. + */ +void md5Step(uint32_t *buffer, uint32_t *input){ + uint32_t AA = buffer[0]; + uint32_t BB = buffer[1]; + uint32_t CC = buffer[2]; + uint32_t DD = buffer[3]; + + uint32_t E; + + unsigned int j; + + for(unsigned int i = 0; i < 64; ++i){ + switch(i / 16){ + case 0: + E = F(BB, CC, DD); + j = i; + break; + case 1: + E = G(BB, CC, DD); + j = ((i * 5) + 1) % 16; + break; + case 2: + E = H(BB, CC, DD); + j = ((i * 3) + 5) % 16; + break; + default: + E = I(BB, CC, DD); + j = (i * 7) % 16; + break; + } + + uint32_t temp = DD; + DD = CC; + CC = BB; + BB = BB + rotateLeft(AA + E + K[i] + input[j], S[i]); + AA = temp; + } + + buffer[0] += AA; + buffer[1] += BB; + buffer[2] += CC; + buffer[3] += DD; +} + +/* + * Functions that run the algorithm on the provided input and put the digest into result. + * result should be able to store 16 bytes. + */ +void md5String(char *input, uint8_t *result){ + MD5Context ctx = {0}; + md5Init(&ctx); + md5Update(&ctx, (uint8_t *)input, strlen(input)); + md5Finalize(&ctx); + + memcpy(result, ctx.digest, 16); +} + +/* +void md5File(FILE *file, uint8_t *result){ + char *input_buffer = malloc(1024); + size_t input_size = 0; + + MD5Context ctx; + md5Init(&ctx); + + while((input_size = fread(input_buffer, 1, 1024, file)) > 0){ + md5Update(&ctx, (uint8_t *)input_buffer, input_size); + } + + md5Finalize(&ctx); + + free(input_buffer); + + memcpy(result, ctx.digest, 16); +} +*/ diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/md5.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/md5.h new file mode 100644 index 0000000..73e3b55 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/md5.h @@ -0,0 +1,29 @@ +#ifndef MD5_H +#define MD5_H + +typedef signed char int8_t; +typedef short int16_t; +typedef int int32_t; +typedef long long int64_t; + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; + +typedef struct{ + uint64_t size; // Size of input in bytes + uint32_t buffer[4]; // Current accumulation of hash + uint8_t input[64]; // Input to be used in the next step + uint8_t digest[16]; // Result of algorithm +}MD5Context; + +void md5Init(MD5Context *ctx); +void md5Update(MD5Context *ctx, uint8_t *input, size_t input_len); +void md5Finalize(MD5Context *ctx); +void md5Step(uint32_t *buffer, uint32_t *input); + +void md5String(char *input, uint8_t *result); +// void md5File(FILE *file, uint8_t *result); + +#endif diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/stb_image.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/stb_image.h new file mode 100644 index 0000000..9eedabe --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/stb_image.h @@ -0,0 +1,7988 @@ +/* stb_image - v2.30 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + +LICENSE + + See end of file for license information. + +RECENT REVISION HISTORY: + + 2.30 (2024-05-31) avoid erroneous gcc warning + 2.29 (2023-05-xx) optimizations + 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff + 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes + 2.26 (2020-07-13) many minor fixes + 2.25 (2020-02-02) fix warnings + 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically + 2.23 (2019-08-11) fix clang static analysis warning + 2.22 (2019-03-04) gif fixes, fix warnings + 2.21 (2019-02-25) fix typo in comment + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings + 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + Arseny Kapoulkine Simon Breuss (16-bit PNM) + John-Mark Allen + Carmelo J Fdez-Aguera + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski + Phil Jordan Dave Moore Roy Eltham + Hayaki Saito Nathan Reed Won Chun + Luke Graham Johan Duparc Nick Verigakis the Horde3D community + Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Eugene Golushkov Laurent Gomila Cort Stratton github:snagar + Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex + Cass Everitt Ryamond Barbiero github:grim210 + Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw + Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus + Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo + Julian Raschke Gregory Mullen Christian Floisand github:darealshinji + Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 + Brad Weinberger Matvey Cherevko github:mosra + Luca Sas Alexander Veselov Zack Middleton [reserved] + Ryan C. Gordon [reserved] [reserved] + DO NOT ADD YOUR NAME HERE + + Jacko Dirks + + To add your name to the credits, pick a random blank space in the middle and fill it. + 80% of merge conflicts on stb PRs are due to people adding their name at the end + of the credits. +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data); +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'desired_channels' if desired_channels is non-zero, or +// *channels_in_file otherwise. If desired_channels is non-zero, +// *channels_in_file has the number of components that _would_ have been +// output otherwise. E.g. if you set desired_channels to 4, you will always +// get RGBA output, but you can check *channels_in_file to see if it's trivially +// opaque because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *channels_in_file will be unchanged. The function +// stbi_failure_reason() can be queried for an extremely brief, end-user +// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS +// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// To query the width, height and component count of an image without having to +// decode the full file, you can use the stbi_info family of functions: +// +// int x,y,n,ok; +// ok = stbi_info(filename, &x, &y, &n); +// // returns ok=1 and sets x, y, n if image is a supported format, +// // 0 otherwise. +// +// Note that stb_image pervasively uses ints in its public API for sizes, +// including sizes of memory buffers. This is now part of the API and thus +// hard to change without causing breakage. As a result, the various image +// loaders all have certain limits on image size; these differ somewhat +// by format but generally boil down to either just under 2GB or just under +// 1GB. When the decoded image would be larger than this, stb_image decoding +// will fail. +// +// Additionally, stb_image will reject image files that have any of their +// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS, +// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit, +// the only way to have an image with such dimensions load correctly +// is for it to have a rather extreme aspect ratio. Either way, the +// assumption here is that such larger images are likely to be malformed +// or malicious. If you do need to load an image with individual dimensions +// larger than that, and it still fits in the overall size limit, you can +// #define STBI_MAX_DIMENSIONS on your own to be something larger. +// +// =========================================================================== +// +// UNICODE: +// +// If compiling for Windows and you wish to use Unicode filenames, compile +// with +// #define STBI_WINDOWS_UTF8 +// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert +// Windows wchar_t filenames to utf8. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy-to-use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// provide more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image supports loading HDR images in general, and currently the Radiance +// .HDR file format specifically. You can still load any file through the existing +// interface; if you attempt to load an HDR file, it will be automatically remapped +// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// We optionally support converting iPhone-formatted PNGs (which store +// premultiplied BGRA) back to RGB, even though they're internally encoded +// differently. To enable this conversion, call +// stbi_convert_iphone_png_to_rgb(1). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// +// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater +// than that size (in either width or height) without further processing. +// This is to let programs in the wild set an upper bound to prevent +// denial-of-service attacks on untrusted data, as one could generate a +// valid image of gigantic dimensions and force stb_image to allocate a +// huge block of memory and spend disproportionate time decoding it. By +// default this is set to (1 << 24), which is 16777216, but that's still +// very big. + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +#include +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef STBIDEF +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + +#ifdef STBI_WINDOWS_UTF8 +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// on most compilers (and ALL modern mainstream compilers) this is threadsafe +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// as above, but only applies to images loaded on the thread that calls the function +// this function is only available if your compiler supports thread-local variables; +// calling it will fail to link if your compiler doesn't +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply); +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert); +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + +#ifdef __cplusplus +#define STBI_EXTERN extern "C" +#else +#define STBI_EXTERN extern +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + +#ifndef STBI_NO_THREAD_LOCALS + #if defined(__cplusplus) && __cplusplus >= 201103L + #define STBI_THREAD_LOCAL thread_local + #elif defined(__GNUC__) && __GNUC__ < 5 + #define STBI_THREAD_LOCAL __thread + #elif defined(_MSC_VER) + #define STBI_THREAD_LOCAL __declspec(thread) + #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) + #define STBI_THREAD_LOCAL _Thread_local + #endif + + #ifndef STBI_THREAD_LOCAL + #if defined(__GNUC__) + #define STBI_THREAD_LOCAL __thread + #endif + #endif +#endif + +#if defined(_MSC_VER) || defined(__SYMBIAN32__) +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#endif + +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif + +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +#ifdef _MSC_VER +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name +#else +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +#ifndef STBI_MAX_DIMENSIONS +#define STBI_MAX_DIMENSIONS (1 << 24) +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + int callback_already_read; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + int ch; + fseek((FILE*) user, n, SEEK_CUR); + ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */ + if (ch != EOF) { + ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */ + } +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user) || ferror((FILE *) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__pnm_is16(stbi__context *s); +#endif + +static +#ifdef STBI_THREAD_LOCAL +STBI_THREAD_LOCAL +#endif +const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +#ifndef STBI_NO_FAILURE_STRINGS +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} +#endif + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} +#endif + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} +#endif + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow. +static int stbi__addints_valid(int a, int b) +{ + if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow + if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0. + return a <= INT_MAX - b; +} + +// returns 1 if the product of two ints fits in a signed short, 0 on overflow. +static int stbi__mul2shorts_valid(int a, int b) +{ + if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow + if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid + if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN + return a >= SHRT_MIN / b; +} + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load_global = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_global = flag_true_if_should_flip; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global +#else +static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; + +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_local = flag_true_if_should_flip; + stbi__vertically_flip_on_load_set = 1; +} + +#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ + ? stbi__vertically_flip_on_load_local \ + : stbi__vertically_flip_on_load_global) +#endif // STBI_THREAD_LOCAL + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + // test the formats with a very explicit header first (at least a FOURCC + // or distinctive magic number first) + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #else + STBI_NOTUSED(bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + + // then the formats that can end up attempting to load with just 1 or 2 + // bytes matching expectations; these are prone to false positives, so + // try them later + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 8) { + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 16) { + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} + +#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); +#endif + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) +// nothing +#else +static void stbi__skip(stbi__context *s, int n) +{ + if (n == 0) return; // already there! + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) +// nothing +#else +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} +#endif + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + z += (stbi__uint32)stbi__get16le(s) << 16; + return z; +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + } + if (n < comp) { + for (i=0; i < x*y; ++i) { + output[i*comp + n] = data[i*comp + n]/255.0f; + } + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) { + for (j=0; j < count[i]; ++j) { + h->size[k++] = (stbi_uc) (i+1); + if(k >= 257) return stbi__err("bad size list","Corrupt JPEG"); + } + } + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + if(c < 0 || c >= 256) // symbol id out of bounds! + return -1; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing + + sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & (sgn - 1)); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG"); + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + diff = t ? stbi__extend_receive(j, t) : 0; + + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG"); + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * (1 << j->succ_low)); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * (1 << shift)); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift)); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + if(n > 256) return stbi__err("bad DHT header","Corrupt JPEG"); // Loop over i < n would write past end of values! + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios + // and I've never seen a non-corrupted JPEG file actually use them + for (i=0; i < s->img_n; ++i) { + if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG"); + if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG"); + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +static stbi_uc stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) +{ + // some JPEGs have junk at end, skip over it but if we find what looks + // like a valid marker, resume there + while (!stbi__at_eof(j->s)) { + stbi_uc x = stbi__get8(j->s); + while (x == 0xff) { // might be a marker + if (stbi__at_eof(j->s)) return STBI__MARKER_none; + x = stbi__get8(j->s); + if (x != 0x00 && x != 0xff) { + // not a stuffed zero or lead-in to another marker, looks + // like an actual marker, return it + return x; + } + // stuffed zero has x=0 now which ends the loop, meaning we go + // back to regular scan loop. + // repeated 0xff keeps trying to read the next byte of the marker. + } + } + return STBI__MARKER_none; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + j->marker = stbi__skip_jpeg_junk_at_end(j); + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + m = stbi__get_marker(j); + if (STBI__RESTART(m)) + m = stbi__get_marker(j); + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + m = stbi__get_marker(j); + } else { + if (!stbi__process_marker(j, m)) return 1; + m = stbi__get_marker(j); + } + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // nothing to do if no components requested; check this now to avoid + // accessing uninitialized coutput[0] later + if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; } + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__errpuc("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) +#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[STBI__ZNSYMS]; + stbi__uint16 value[STBI__ZNSYMS]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + int hit_zeof_once; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static int stbi__zeof(stbi__zbuf *z) +{ + return (z->zbuffer >= z->zbuffer_end); +} + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + return stbi__zeof(z) ? 0 : *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + if (z->code_buffer >= (1U << z->num_bits)) { + z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */ + return; + } + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s >= 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere! + if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) { + if (stbi__zeof(a)) { + if (!a->hit_zeof_once) { + // This is the first time we hit eof, insert 16 extra padding btis + // to allow us to keep going; if we actually consume any of them + // though, that is invalid data. This is caught later. + a->hit_zeof_once = 1; + a->num_bits += 16; // add 16 implicit zero bits + } else { + // We already inserted our extra 16 padding bits and are again + // out, this stream is actually prematurely terminated. + return -1; + } + } else { + stbi__fill_bits(a); + } + } + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + unsigned int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (unsigned int) (z->zout - z->zout_start); + limit = old_limit = (unsigned) (z->zout_end - z->zout_start); + if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory"); + while (cur + n > limit) { + if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory"); + limit *= 2; + } + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + if (a->hit_zeof_once && a->num_bits < 16) { + // The first time we hit zeof, we inserted 16 extra zero bits into our bit + // buffer so the decoder can just do its speculative decoding. But if we + // actually consumed any of those bits (which is the case when num_bits < 16), + // the stream actually read past the end so it is malformed. + return stbi__err("unexpected end","Corrupt PNG"); + } + return 1; + } + if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0 || z >= 30) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (len > a->zout_end - zout) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + } else if (c == 18) { + c = stbi__zreceive(a,7)+11; + } else { + return stbi__err("bad codelengths", "Corrupt PNG"); + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG"); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + a->hit_zeof_once = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filter used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_sub // Paeth with b=c=0 turns out to be equivalent to sub +}; + +static int stbi__paeth(int a, int b, int c) +{ + // This formulation looks very different from the reference in the PNG spec, but is + // actually equivalent and has favorable data dependencies and admits straightforward + // generation of branch-free code, which helps performance significantly. + int thresh = c*3 - (a + b); + int lo = a < b ? a : b; + int hi = a < b ? b : a; + int t0 = (hi <= thresh) ? lo : c; + int t1 = (thresh <= lo) ? hi : t0; + return t1; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// adds an extra all-255 alpha channel +// dest == src is legal +// img_n must be 1 or 3 +static void stbi__create_png_alpha_expand8(stbi_uc *dest, stbi_uc *src, stbi__uint32 x, int img_n) +{ + int i; + // must process data backwards since we allow dest==src + if (img_n == 1) { + for (i=x-1; i >= 0; --i) { + dest[i*2+1] = 255; + dest[i*2+0] = src[i]; + } + } else { + STBI_ASSERT(img_n == 3); + for (i=x-1; i >= 0; --i) { + dest[i*4+3] = 255; + dest[i*4+2] = src[i*3+2]; + dest[i*4+1] = src[i*3+1]; + dest[i*4+0] = src[i*3+0]; + } + } +} + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16 ? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + stbi_uc *filter_buf; + int all_ok = 1; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + // note: error exits here don't need to clean up a->out individually, + // stbi__do_png always does on error. + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + if (!stbi__mad2sizes_valid(img_width_bytes, y, img_width_bytes)) return stbi__err("too large", "Corrupt PNG"); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + // Allocate two scan lines worth of filter workspace buffer. + filter_buf = (stbi_uc *) stbi__malloc_mad2(img_width_bytes, 2, 0); + if (!filter_buf) return stbi__err("outofmem", "Out of memory"); + + // Filtering for low-bit-depth images + if (depth < 8) { + filter_bytes = 1; + width = img_width_bytes; + } + + for (j=0; j < y; ++j) { + // cur/prior filter buffers alternate + stbi_uc *cur = filter_buf + (j & 1)*img_width_bytes; + stbi_uc *prior = filter_buf + (~j & 1)*img_width_bytes; + stbi_uc *dest = a->out + stride*j; + int nk = width * filter_bytes; + int filter = *raw++; + + // check filter type + if (filter > 4) { + all_ok = stbi__err("invalid filter","Corrupt PNG"); + break; + } + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // perform actual filtering + switch (filter) { + case STBI__F_none: + memcpy(cur, raw, nk); + break; + case STBI__F_sub: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); + break; + case STBI__F_up: + for (k = 0; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); + break; + case STBI__F_avg: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); + break; + case STBI__F_paeth: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0) + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes], prior[k], prior[k-filter_bytes])); + break; + case STBI__F_avg_first: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); + break; + } + + raw += nk; + + // expand decoded bits in cur to dest, also adding an extra alpha channel if desired + if (depth < 8) { + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + stbi_uc *in = cur; + stbi_uc *out = dest; + stbi_uc inb = 0; + stbi__uint32 nsmp = x*img_n; + + // expand bits to bytes first + if (depth == 4) { + for (i=0; i < nsmp; ++i) { + if ((i & 1) == 0) inb = *in++; + *out++ = scale * (inb >> 4); + inb <<= 4; + } + } else if (depth == 2) { + for (i=0; i < nsmp; ++i) { + if ((i & 3) == 0) inb = *in++; + *out++ = scale * (inb >> 6); + inb <<= 2; + } + } else { + STBI_ASSERT(depth == 1); + for (i=0; i < nsmp; ++i) { + if ((i & 7) == 0) inb = *in++; + *out++ = scale * (inb >> 7); + inb <<= 1; + } + } + + // insert alpha=255 values if desired + if (img_n != out_n) + stbi__create_png_alpha_expand8(dest, dest, x, img_n); + } else if (depth == 8) { + if (img_n == out_n) + memcpy(dest, cur, x*img_n); + else + stbi__create_png_alpha_expand8(dest, cur, x, img_n); + } else if (depth == 16) { + // convert the image data from big-endian to platform-native + stbi__uint16 *dest16 = (stbi__uint16*)dest; + stbi__uint32 nsmp = x*img_n; + + if (img_n == out_n) { + for (i = 0; i < nsmp; ++i, ++dest16, cur += 2) + *dest16 = (cur[0] << 8) | cur[1]; + } else { + STBI_ASSERT(img_n+1 == out_n); + if (img_n == 1) { + for (i = 0; i < x; ++i, dest16 += 2, cur += 2) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = 0xffff; + } + } else { + STBI_ASSERT(img_n == 3); + for (i = 0; i < x; ++i, dest16 += 4, cur += 6) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = (cur[2] << 8) | cur[3]; + dest16[2] = (cur[4] << 8) | cur[5]; + dest16[3] = 0xffff; + } + } + } + } + } + + STBI_FREE(filter_buf); + if (!all_ok) return 0; + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + if (!final) return stbi__err("outofmem", "Out of memory"); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load_global = 0; +static int stbi__de_iphone_flag_global = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_global = flag_true_if_should_convert; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global +#define stbi__de_iphone_flag stbi__de_iphone_flag_global +#else +static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; +static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; + +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; + stbi__unpremultiply_on_load_set = 1; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_local = flag_true_if_should_convert; + stbi__de_iphone_flag_set = 1; +} + +#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \ + ? stbi__unpremultiply_on_load_local \ + : stbi__unpremultiply_on_load_global) +#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \ + ? stbi__de_iphone_flag_local \ + : stbi__de_iphone_flag_global) +#endif // STBI_THREAD_LOCAL + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]={0}; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); + s->img_y = stbi__get32be(s); + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + } + // even with SCAN_header, have to scan to see if we have a tRNS + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now. + if (scan == STBI__SCAN_header) { ++s->img_n; return 1; } + if (z->depth == 16) { + for (k = 0; k < s->img_n && k < 3; ++k) // extra loop test to suppress false GCC warning + tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n && k < 3; ++k) + tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { + // header scan definitely stops at first IDAT + if (pal_img_n) + s->img_n = pal_img_n; + return 1; + } + if (c.length > (1u << 30)) return stbi__err("IDAT size limit", "IDAT section larger than 2^30 bytes"); + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth <= 8) + ri->bits_per_channel = 8; + else if (p->depth == 16) + ri->bits_per_channel = 16; + else + return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth"); + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) { n += 16; z >>= 16; } + if (z >= 0x00100) { n += 8; z >>= 8; } + if (z >= 0x00010) { n += 4; z >>= 4; } + if (z >= 0x00004) { n += 2; z >>= 2; } + if (z >= 0x00002) { n += 1;/* >>= 1;*/ } + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(unsigned int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; + int extra_read; +} stbi__bmp_data; + +static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress) +{ + // BI_BITFIELDS specifies masks explicitly, don't override + if (compress == 3) + return 1; + + if (compress == 0) { + if (info->bpp == 16) { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } else if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + // otherwise, use defaults, which is all-0 + info->mr = info->mg = info->mb = info->ma = 0; + } + return 1; + } + return 0; // error +} + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + info->extra_read = 14; + + if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP"); + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes + if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + stbi__bmp_set_mask_defaults(info, compress); + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->extra_read += 12; + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + // V4/V5 header + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs + stbi__bmp_set_mask_defaults(info, compress); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - info.extra_read - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - info.extra_read - info.hsz) >> 2; + } + if (psize == 0) { + // accept some number of extra bytes after the header, but if the offset points either to before + // the header ends or implies a large amount of extra data, reject the file as malformed + int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original); + int header_limit = 1024; // max we actually read is below 256 bytes currently. + int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size. + if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) { + return stbi__errpuc("bad header", "Corrupt BMP"); + } + // we established that bytes_read_so_far is positive and sensible. + // the first half of this test rejects offsets that are either too small positives, or + // negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn + // ensures the number computed in the second half of the test can't overflow. + if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } else { + stbi__skip(s, info.offset - bytes_read_so_far); + } + } + + if (info.bpp == 24 && ma == 0xff000000) + s->img_n = 3; + else + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - info.extra_read - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i]; p1[i] = p2[i]; p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + STBI_NOTUSED(tga_x_origin); // @TODO + STBI_NOTUSED(tga_y_origin); // @TODO + + if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + if (tga_palette_len == 0) { /* you have to have at least one entry! */ + STBI_FREE(tga_data); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + STBI_NOTUSED(tga_palette_start); + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + + if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + if (!result) return stbi__errpuc("outofmem", "Out of memory"); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!g) return stbi__err("outofmem", "Out of memory"); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + STBI_NOTUSED(req_comp); + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) + return stbi__errpuc("too large", "GIF image is too large"); + pcount = g->w * g->h; + g->out = (stbi_uc *) stbi__malloc(4 * pcount); + g->background = (stbi_uc *) stbi__malloc(4 * pcount); + g->history = (stbi_uc *) stbi__malloc(pcount); + if (!g->out || !g->background || !g->history) + return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "transparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to the color that was there the previous frame. + memset(g->out, 0x00, 4 * pcount); + memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) + memset(g->history, 0x00, pcount); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispose of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + // if the width of the specified rectangle is 0, that means + // we may not see *any* pixels or the image is malformed; + // to make sure this is caught, move the current y down to + // max_y (which is what out_gif_code checks). + if (w == 0) + g->cur_y = g->max_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (!o) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays) +{ + STBI_FREE(g->out); + STBI_FREE(g->history); + STBI_FREE(g->background); + + if (out) STBI_FREE(out); + if (delays && *delays) STBI_FREE(*delays); + return stbi__errpuc("outofmem", "Out of memory"); +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + int out_size = 0; + int delays_size = 0; + + STBI_NOTUSED(out_size); + STBI_NOTUSED(delays_size); + + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); + if (!tmp) + return stbi__load_gif_main_outofmem(&g, out, delays); + else { + out = (stbi_uc*) tmp; + out_size = layers * stride; + } + + if (delays) { + int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); + if (!new_delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + *delays = new_delays; + delays_size = layers * sizeof(int); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (!out) + return stbi__load_gif_main_outofmem(&g, out, delays); + out_size = layers * stride; + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + if (!*delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + delays_size = layers * sizeof(int); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + STBI_NOTUSED(ri); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } else if (g.out) { + // if there was an error and we allocated an image buffer, free it! + STBI_FREE(g.out); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + if (p == NULL) { + stbi__rewind( s ); + return 0; + } + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) { + if (info.bpp == 24 && info.ma == 0xff000000) + *comp = 3; + else + *comp = info.ma ? 4 : 3; + } + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + STBI_NOTUSED(stbi__get32be(s)); + STBI_NOTUSED(stbi__get32be(s)); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n); + if (ri->bits_per_channel == 0) + return 0; + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) { + STBI_FREE(out); + return stbi__errpuc("bad PNM", "PNM file truncated"); + } + + if (req_comp && req_comp != s->img_n) { + if (ri->bits_per_channel == 16) { + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y); + } else { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + } + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + if((value > 214748364) || (value == 214748364 && *c > '7')) + return stbi__err("integer parse overflow", "Parsing an integer in the PPM header overflowed a 32-bit int"); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + if(*x == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + if (*y == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + if (maxv > 65535) + return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images"); + else if (maxv > 255) + return 16; + else + return 8; +} + +static int stbi__pnm_is16(stbi__context *s) +{ + if (stbi__pnm_info(s, NULL, NULL, NULL) == 16) + return 1; + return 0; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_is16(s)) return 1; + #endif + return 0; +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/stb_image_impl.cpp b/CristalDiskMark/source/CrystalDiskMark/Priscilla/stb_image_impl.cpp new file mode 100644 index 0000000..0611d54 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/stb_image_impl.cpp @@ -0,0 +1,17 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#define STBI_ONLY_PNG +#define STBI_NO_STDIO +#define STBI_NO_HDR +#define STBI_NO_LINEAR +#define STBI_NO_FAILURE_STRINGS +#define STBI_ASSERT(x) + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/stb_image_write.h b/CristalDiskMark/source/CrystalDiskMark/Priscilla/stb_image_write.h new file mode 100644 index 0000000..e4b32ed --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/stb_image_write.h @@ -0,0 +1,1724 @@ +/* stb_image_write - v1.16 - public domain - http://nothings.org/stb + writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 + no warranty implied; use at your own risk + + Before #including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + + in the file that you want to have the implementation. + + Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + + This header file is a library for writing images to C stdio or a callback. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation; though providing a custom + zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. + This library is designed for source code compactness and simplicity, + not optimal image file size or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can #define STBIW_MEMMOVE() to replace memmove() + You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function + for PNG compression (instead of the builtin one), it must have the following signature: + unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); + The returned data will be freed with STBIW_FREE() (free() by default), + so it must be heap allocated with STBIW_MALLOC() (malloc() by default), + +UNICODE: + + If compiling for Windows and you wish to use Unicode filenames, compile + with + #define STBIW_WINDOWS_UTF8 + and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert + Windows wchar_t filenames to utf8. + +USAGE: + + There are five functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + + void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically + + There are also five equivalent functions that use an arbitrary write function. You are + expected to open/close your file-equivalent before and after calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can configure it with these global variables: + int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE + int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression + int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode + + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP format expands Y to RGB in the file format and does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) + + PNG allows you to set the deflate compression level by setting the global + variable 'stbi_write_png_compression_level' (it defaults to 8). + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + + JPEG does ignore alpha channels in input data; quality is between 1 and 100. + Higher quality looks better but results in a bigger image. + JPEG baseline (no JPEG progressive). + +CREDITS: + + + Sean Barrett - PNG/BMP/TGA + Baldur Karlsson - HDR + Jean-Sebastien Guay - TGA monochrome + Tim Kelsey - misc enhancements + Alan Hickman - TGA RLE + Emmanuel Julien - initial file IO callback implementation + Jon Olick - original jo_jpeg.cpp code + Daniel Gibson - integrate JPEG, allow external zlib + Aarni Koskela - allow choosing PNG filter + + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + github:poppolopoppo + Patrick Boettcher + github:xeekworx + Cap Petschulat + Simon Rodriguez + Ivan Tikhonov + github:ignotion + Adam Schackart + Andrew Kensler + +LICENSE + + See end of file for license information. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#include + +// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' +#ifndef STBIWDEF +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#ifdef __cplusplus +#define STBIWDEF extern "C" +#else +#define STBIWDEF extern +#endif +#endif +#endif + +#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations +STBIWDEF int stbi_write_tga_with_rle; +STBIWDEF int stbi_write_png_compression_level; +STBIWDEF int stbi_write_force_png_filter; +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); + +#ifdef STBIW_WINDOWS_UTF8 +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + +STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#include +#include +#include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,newsz) realloc(p,newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#endif + + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +#ifdef STB_IMAGE_WRITE_STATIC +static int stbi_write_png_compression_level = 8; +static int stbi_write_tga_with_rle = 1; +static int stbi_write_force_png_filter = -1; +#else +int stbi_write_png_compression_level = 8; +int stbi_write_tga_with_rle = 1; +int stbi_write_force_png_filter = -1; +#endif + +static int stbi__flip_vertically_on_write = 0; + +STBIWDEF void stbi_flip_vertically_on_write(int flag) +{ + stbi__flip_vertically_on_write = flag; +} + +typedef struct +{ + stbi_write_func *func; + void *context; + unsigned char buffer[64]; + int buf_used; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) +{ + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) +{ + fwrite(data,1,size,(FILE*) context); +} + +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) +#ifdef __cplusplus +#define STBIW_EXTERN extern "C" +#else +#define STBIW_EXTERN extern +#endif +STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); + +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbiw__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = stbiw__fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) +{ + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context,&x,1); + break; } + case '2': { int x = va_arg(v,int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x>>8); + s->func(s->context,b,2); + break; } + case '4': { stbiw_uint32 x = va_arg(v,int); + unsigned char b[4]; + b[0]=STBIW_UCHAR(x); + b[1]=STBIW_UCHAR(x>>8); + b[2]=STBIW_UCHAR(x>>16); + b[3]=STBIW_UCHAR(x>>24); + s->func(s->context,b,4); + break; } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) +{ + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__write_flush(stbi__write_context *s) +{ + if (s->buf_used) { + s->func(s->context, &s->buffer, s->buf_used); + s->buf_used = 0; + } +} + +static void stbiw__putc(stbi__write_context *s, unsigned char c) +{ + s->func(s->context, &c, 1); +} + +static void stbiw__write1(stbi__write_context *s, unsigned char a) +{ + if ((size_t)s->buf_used + 1 > sizeof(s->buffer)) + stbiw__write_flush(s); + s->buffer[s->buf_used++] = a; +} + +static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) +{ + int n; + if ((size_t)s->buf_used + 3 > sizeof(s->buffer)) + stbiw__write_flush(s); + n = s->buf_used; + s->buf_used = n+3; + s->buffer[n+0] = a; + s->buffer[n+1] = b; + s->buffer[n+2] = c; +} + +static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + int k; + + if (write_alpha < 0) + stbiw__write1(s, d[comp - 1]); + + switch (comp) { + case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case + case 1: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + stbiw__write1(s, d[0]); // monochrome TGA + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k = 0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) + stbiw__write1(s, d[comp - 1]); +} + +static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) +{ + stbiw_uint32 zero = 0; + int i,j, j_end; + + if (y <= 0) + return; + + if (stbi__flip_vertically_on_write) + vdir *= -1; + + if (vdir < 0) { + j_end = -1; j = y-1; + } else { + j_end = y; j = 0; + } + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + unsigned char *d = (unsigned char *) data + (j*x+i)*comp; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + stbiw__write_flush(s); + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) +{ + if (y < 0 || x < 0) { + return 0; + } else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) +{ + if (comp != 4) { + // write RGB bitmap + int pad = (-x*3) & 3; + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header + } else { + // RGBA bitmaps need a v4 header + // use BI_BITFIELDS mode with 32bpp and alpha mask + // (straight BI_RGB with alpha mask doesn't work in most readers) + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *)data,1,0, + "11 4 22 4" "4 44 22 444444 4444 4 444 444 444 444", + 'B', 'M', 14+108+x*y*4, 0, 0, 14+108, // file header + 108, x,y, 1,32, 3,0,0,0,0,0, 0xff0000,0xff00,0xff,0xff000000u, 0, 0,0,0, 0,0,0, 0,0,0, 0,0,0); // bitmap V4 header + } +} + +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif //!STBI_WRITE_NO_STDIO + +static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) +{ + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp-1 : comp; + int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) + return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); + } else { + int i,j,k; + int jend, jdir; + + stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); + + if (stbi__flip_vertically_on_write) { + j = 0; + jend = y; + jdir = 1; + } else { + j = y-1; + jend = -1; + jdir = -1; + } + for (; j != jend; j += jdir) { + unsigned char *row = (unsigned char *) data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char *begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char *prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } else { + --len; + break; + } + } + } else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + stbiw__write1(s, header); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } else { + unsigned char header = STBIW_UCHAR(len - 129); + stbiw__write1(s, header); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + stbiw__write_flush(s); + } + return 1; +} + +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, (void *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson + +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +#ifndef STBI_WRITE_NO_STDIO + +static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) +{ + unsigned char lengthbyte = STBIW_UCHAR(length+128); + STBIW_ASSERT(length+128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) +{ + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width&0xff00)>>8; + scanlineheader[3] = (width&0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x=0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } else { + int c,r; + /* encode into scratch buffer */ + for (x=0; x < width; x++) { + switch(ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width*0] = rgbe[0]; + scratch[x + width*1] = rgbe[1]; + scratch[x + width*2] = rgbe[2]; + scratch[x + width*3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c=0; c < 4; c++) { + unsigned char *comp = &scratch[width*c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r+2 < width) { + if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) + break; + ++r; + } + if (r+2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r-x; + if (len > 128) len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r-x; + if (len > 127) len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) +{ + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full output scanline. + unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + int i, len; + char buffer[128]; + char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header)-1); + +#ifdef __STDC_LIB_EXT1__ + len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#else + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#endif + s->func(s->context, buffer, len); + + for(i=0; i < y; i++) + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); + STBIW_FREE(scratch); + return 1; + } +} + +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float *) data); +} + +STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif // STBI_WRITE_NO_STDIO + + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +#ifndef STBIW_ZLIB_COMPRESS +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() +#define stbiw__sbraw(a) ((int *) (void *) (a) - 2) +#define stbiw__sbm(a) stbiw__sbraw(a)[0] +#define stbiw__sbn(a) stbiw__sbraw(a)[1] + +#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) +#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) +#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) +#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) + +static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; + void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbiw__zhash(unsigned char *data) +{ + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbiw__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) +// default huffman tables +#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) +#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) +#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) +#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) +#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +#define stbiw__ZHASH 16384 + +#endif // STBIW_ZLIB_COMPRESS + +STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ +#ifdef STBIW_ZLIB_COMPRESS + // user provided a zlib compress implementation, use that + return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); +#else // use builtin + static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; + static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; + static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**)); + if (hash_table == NULL) + return NULL; + if (quality < 5) quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1,1); // BFINAL = 1 + stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbiw__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) { best=d; bestloc=hlist[j]; } + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__sbpush(hash_table[h],data+i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal + h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int) (data+i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbiw__zlib_huff(j+257); + if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); + if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbiw__zlib_add(0,1); + + for (i=0; i < stbiw__ZHASH; ++i) + (void) stbiw__sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + // store uncompressed instead if compression was worse + if (stbiw__sbn(out) > data_len + 2 + ((data_len+32766)/32767)*5) { + stbiw__sbn(out) = 2; // truncate to DEFLATE 32K window and FLEVEL = 1 + for (j = 0; j < data_len;) { + int blocklen = data_len - j; + if (blocklen > 32767) blocklen = 32767; + stbiw__sbpush(out, data_len - j == blocklen); // BFINAL = ?, BTYPE = 0 -- no compression + stbiw__sbpush(out, STBIW_UCHAR(blocklen)); // LEN + stbiw__sbpush(out, STBIW_UCHAR(blocklen >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(~blocklen)); // NLEN + stbiw__sbpush(out, STBIW_UCHAR(~blocklen >> 8)); + memcpy(out+stbiw__sbn(out), data+j, blocklen); + stbiw__sbn(out) += blocklen; + j += blocklen; + } + } + + { + // compute adler32 on input + unsigned int s1=1, s2=0; + int blocklen = (int) (data_len % 5552); + j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; } + s1 %= 65521; s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char *) stbiw__sbraw(out); +#endif // STBIW_ZLIB_COMPRESS +} + +static unsigned int stbiw__crc32(unsigned char *buffer, int len) +{ +#ifdef STBIW_CRC32 + return STBIW_CRC32(buffer, len); +#else + static unsigned int crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + unsigned int crc = ~0u; + int i; + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +#endif +} + +#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) +#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbiw__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbiw__crc32(*data - len - 4, len+4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__paeth(int a, int b, int c) +{ + int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); + if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); + if (pb <= pc) return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +// @OPTIMIZE: provide an option that always forces left-predict or paeth predict +static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) +{ + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = (y != 0) ? mapping : firstmap; + int i; + int type = mymap[filter_type]; + unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); + int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; + + if (type==0) { + memcpy(line_buffer, z, width*n); + return; + } + + // first loop isn't optimized since it's just one pixel + for (i = 0; i < n; ++i) { + switch (type) { + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + } + switch (type) { + case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; + case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; + case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; + case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; + } +} + +STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int force_filter = stbi_write_force_png_filter; + int ctype[5] = { -1, 0, 4, 2, 6 }; + unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; + unsigned char *out,*o, *filt, *zlib; + signed char *line_buffer; + int j,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + if (force_filter >= 5) { + force_filter = -1; + } + + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } + for (j=0; j < y; ++j) { + int filter_type; + if (force_filter > -1) { + filter_type = force_filter; + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); + } else { // Estimate the best filter by running through all of them: + int best_filter = 0, best_filter_val = 0x7fffffff, est, i; + for (filter_type = 0; filter_type < 5; filter_type++) { + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); + + // Estimate the entropy of the line using this filter; the less, the better. + est = 0; + for (i = 0; i < x*n; ++i) { + est += abs((signed char) line_buffer[i]); + } + if (est < best_filter_val) { + best_filter_val = est; + best_filter = filter_type; + } + } + if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); + filter_type = best_filter; + } + } + // when we get here, filter_type contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) filter_type; + STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); + STBIW_FREE(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + STBIW_MEMMOVE(o,sig,8); o+= 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o,13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o,0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o,0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) +{ + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + + f = stbiw__fopen(filename, "wb"); + if (!f) { STBIW_FREE(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +#endif + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) +{ + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + + +/* *************************************************************************** + * + * JPEG writer + * + * This is based on Jon Olick's jo_jpeg.cpp: + * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html + */ + +static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, + 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; + +static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { + int bitBuf = *bitBufP, bitCnt = *bitCntP; + bitCnt += bs[1]; + bitBuf |= bs[0] << (24 - bitCnt); + while(bitCnt >= 8) { + unsigned char c = (bitBuf >> 16) & 255; + stbiw__putc(s, c); + if(c == 255) { + stbiw__putc(s, 0); + } + bitBuf <<= 8; + bitCnt -= 8; + } + *bitBufP = bitBuf; + *bitCntP = bitCnt; +} + +static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { + float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; + float z1, z2, z3, z4, z5, z11, z13; + + float tmp0 = d0 + d7; + float tmp7 = d0 - d7; + float tmp1 = d1 + d6; + float tmp6 = d1 - d6; + float tmp2 = d2 + d5; + float tmp5 = d2 - d5; + float tmp3 = d3 + d4; + float tmp4 = d3 - d4; + + // Even part + float tmp10 = tmp0 + tmp3; // phase 2 + float tmp13 = tmp0 - tmp3; + float tmp11 = tmp1 + tmp2; + float tmp12 = tmp1 - tmp2; + + d0 = tmp10 + tmp11; // phase 3 + d4 = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * 0.707106781f; // c4 + d2 = tmp13 + z1; // phase 5 + d6 = tmp13 - z1; + + // Odd part + tmp10 = tmp4 + tmp5; // phase 2 + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + z5 = (tmp10 - tmp12) * 0.382683433f; // c6 + z2 = tmp10 * 0.541196100f + z5; // c2-c6 + z4 = tmp12 * 1.306562965f + z5; // c2+c6 + z3 = tmp11 * 0.707106781f; // c4 + + z11 = tmp7 + z3; // phase 5 + z13 = tmp7 - z3; + + *d5p = z13 + z2; // phase 6 + *d3p = z13 - z2; + *d1p = z11 + z4; + *d7p = z11 - z4; + + *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; +} + +static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { + int tmp1 = val < 0 ? -val : val; + val = val < 0 ? val-1 : val; + bits[1] = 1; + while(tmp1 >>= 1) { + ++bits[1]; + } + bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { + } + // end0pos = first element in reverse order !=0 + if(end0pos == 0) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + return DU[0]; + } + for(i = 1; i <= end0pos; ++i) { + int startpos = i; + int nrzeroes; + unsigned short bits[2]; + for (; DU[i]==0 && i<=end0pos; ++i) { + } + nrzeroes = i-startpos; + if ( nrzeroes >= 16 ) { + int lng = nrzeroes>>4; + int nrmarker; + for (nrmarker=1; nrmarker <= lng; ++nrmarker) + stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); + nrzeroes &= 15; + } + stbiw__jpg_calcBits(DU[i], bits); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); + } + if(end0pos != 63) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + } + return DU[0]; +} + +static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { + // Constants that don't pollute global namespace + static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; + static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; + static const unsigned char std_ac_luminance_values[] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; + static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; + static const unsigned char std_ac_chrominance_values[] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + // Huffman tables + static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; + static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; + static const unsigned short YAC_HT[256][2] = { + {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const unsigned short UVAC_HT[256][2] = { + {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, + 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; + static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; + static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, + 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; + + int row, col, i, k, subsample; + float fdtbl_Y[64], fdtbl_UV[64]; + unsigned char YTable[64], UVTable[64]; + + if(!data || !width || !height || comp > 4 || comp < 1) { + return 0; + } + + quality = quality ? quality : 90; + subsample = quality <= 90 ? 1 : 0; + quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; + quality = quality < 50 ? 5000 / quality : 200 - quality * 2; + + for(i = 0; i < 64; ++i) { + int uvti, yti = (YQT[i]*quality+50)/100; + YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); + uvti = (UVQT[i]*quality+50)/100; + UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); + } + + for(row = 0, k = 0; row < 8; ++row) { + for(col = 0; col < 8; ++col, ++k) { + fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + } + } + + // Write Headers + { + static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; + static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; + const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width), + 3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; + s->func(s->context, (void*)head0, sizeof(head0)); + s->func(s->context, (void*)YTable, sizeof(YTable)); + stbiw__putc(s, 1); + s->func(s->context, UVTable, sizeof(UVTable)); + s->func(s->context, (void*)head1, sizeof(head1)); + s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); + s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); + stbiw__putc(s, 0x10); // HTYACinfo + s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); + s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); + stbiw__putc(s, 1); // HTUDCinfo + s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); + stbiw__putc(s, 0x11); // HTUACinfo + s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); + s->func(s->context, (void*)head2, sizeof(head2)); + } + + // Encode 8x8 macroblocks + { + static const unsigned short fillBits[] = {0x7F, 7}; + int DCY=0, DCU=0, DCV=0; + int bitBuf=0, bitCnt=0; + // comp == 2 is grey+alpha (alpha is ignored) + int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; + const unsigned char *dataR = (const unsigned char *)data; + const unsigned char *dataG = dataR + ofsG; + const unsigned char *dataB = dataR + ofsB; + int x, y, pos; + if(subsample) { + for(y = 0; y < height; y += 16) { + for(x = 0; x < width; x += 16) { + float Y[256], U[256], V[256]; + for(row = y, pos = 0; row < y+16; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+16; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + + // subsample U,V + { + float subU[64], subV[64]; + int yy, xx; + for(yy = 0, pos = 0; yy < 8; ++yy) { + for(xx = 0; xx < 8; ++xx, ++pos) { + int j = yy*32+xx*2; + subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f; + subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f; + } + } + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + } else { + for(y = 0; y < height; y += 8) { + for(x = 0; x < width; x += 8) { + float Y[64], U[64], V[64]; + for(row = y, pos = 0; row < y+8; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+8; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + + // Do the bit alignment of the EOI marker + stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); + } + + // EOI + stbiw__putc(s, 0xFF); + stbiw__putc(s, 0xD9); + + return 1; +} + +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); +} + + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.16 (2021-07-11) + make Deflate code emit uncompressed blocks when it would otherwise expand + support writing BMPs with alpha channel + 1.15 (2020-07-13) unknown + 1.14 (2020-02-02) updated JPEG writer to downsample chroma channels + 1.13 + 1.12 + 1.11 (2019-08-11) + + 1.10 (2019-02-07) + support utf8 filenames in Windows; fix warnings and platform ifdefs + 1.09 (2018-02-11) + fix typo in zlib quality API, improve STB_I_W_STATIC in C++ + 1.08 (2018-01-29) + add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter + 1.07 (2017-07-24) + doc fix + 1.06 (2017-07-23) + writing JPEG (using Jon Olick's code) + 1.05 ??? + 1.04 (2017-03-03) + monochrome BMP expansion + 1.03 ??? + 1.02 (2016-04-02) + avoid allocating large structures on the stack + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/CristalDiskMark/source/CrystalDiskMark/Priscilla/stb_image_write_impl.cpp b/CristalDiskMark/source/CrystalDiskMark/Priscilla/stb_image_write_impl.cpp new file mode 100644 index 0000000..f075420 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/Priscilla/stb_image_write_impl.cpp @@ -0,0 +1,13 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#define STBI_NO_STDIO +#define STBI_WRITE_NO_STDIO +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + diff --git a/CristalDiskMark/source/CrystalDiskMark/SettingsDlg.cpp b/CristalDiskMark/source/CrystalDiskMark/SettingsDlg.cpp new file mode 100644 index 0000000..d7e7844 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/SettingsDlg.cpp @@ -0,0 +1,700 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#include "stdafx.h" +#include "DiskMark.h" +#include "DiskMarkDlg.h" +#include "SettingsDlg.h" + +IMPLEMENT_DYNCREATE(CSettingsDlg, CDialog) + +CSettingsDlg::CSettingsDlg(CWnd* pParent /*=NULL*/) + : CDialogFx(CSettingsDlg::IDD, pParent) +{ + CMainDialogFx* p = (CMainDialogFx*)pParent; + + m_ZoomType = p->GetZoomType(); + m_FontScale = p->GetFontScale(); + m_FontRatio = p->GetFontRatio(); + m_FontFace = p->GetFontFace(); + m_FontRender = p->GetFontRender(); + m_CurrentLangPath = p->GetCurrentLangPath(); + m_DefaultLangPath = p->GetDefaultLangPath(); + m_ThemeDir = p->GetThemeDir(); + m_CurrentTheme = p->GetCurrentTheme(); + m_DefaultTheme = p->GetDefaultTheme(); + m_Ini = p->GetIniPath(); + + m_Profile = ((CDiskMarkDlg*)pParent)->m_Profile; + m_MeasureTime = ((CDiskMarkDlg*)pParent)->m_MeasureTime; + m_IntervalTime = ((CDiskMarkDlg*)pParent)->m_IntervalTime; + m_TestData = ((CDiskMarkDlg*)pParent)->m_TestData; +} + +CSettingsDlg::~CSettingsDlg() +{ +} + +void CSettingsDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialogFx::DoDataExchange(pDX); + + DDX_Control(pDX, IDC_LABEL_TYPE, m_LabelType); + DDX_Control(pDX, IDC_LABEL_SIZE, m_LabelSize); + DDX_Control(pDX, IDC_LABEL_QUEUES, m_LabelQueues); + DDX_Control(pDX, IDC_LABEL_THREADS, m_LabelThreads); + DDX_Control(pDX, IDC_LABEL_DEFAULT, m_LabelDefault); + DDX_Control(pDX, IDC_LABEL_PEAK, m_LabelPeak); + DDX_Control(pDX, IDC_LABEL_DEMO, m_LabelDemo); + + DDX_Control(pDX, IDC_LABEL_MEASURE_TIME, m_LabelMeasureTime); + DDX_Control(pDX, IDC_LABEL_INTERVAL_TIME, m_LabelIntervalTime); +// DDX_Control(pDX, IDC_LABEL_AFFINITY, m_LabelAffinity); +// DDX_Control(pDX, IDC_LABEL_DATA, m_LabelData); + + DDX_Control(pDX, IDC_SET_DEFAULT, m_ButtonSetDefault); + DDX_Control(pDX, IDC_SET_NVME_8, m_ButtonSetNVMe8); + DDX_Control(pDX, IDC_SET_FLASH_MEMORY, m_ButtonSetFlashMemory); + + DDX_Control(pDX, IDC_COMBO_BENCH_TYPE_0, m_ComboBenchType0); + DDX_Control(pDX, IDC_COMBO_BENCH_TYPE_1, m_ComboBenchType1); + DDX_Control(pDX, IDC_COMBO_BENCH_TYPE_2, m_ComboBenchType2); + DDX_Control(pDX, IDC_COMBO_BENCH_TYPE_3, m_ComboBenchType3); + DDX_Control(pDX, IDC_COMBO_BENCH_TYPE_4, m_ComboBenchType4); + DDX_Control(pDX, IDC_COMBO_BENCH_TYPE_5, m_ComboBenchType5); + DDX_Control(pDX, IDC_COMBO_BENCH_TYPE_8, m_ComboBenchType8); + + DDX_Control(pDX, IDC_COMBO_BENCH_SIZE_0, m_ComboBenchSize0); + DDX_Control(pDX, IDC_COMBO_BENCH_SIZE_1, m_ComboBenchSize1); + DDX_Control(pDX, IDC_COMBO_BENCH_SIZE_2, m_ComboBenchSize2); + DDX_Control(pDX, IDC_COMBO_BENCH_SIZE_3, m_ComboBenchSize3); + DDX_Control(pDX, IDC_COMBO_BENCH_SIZE_4, m_ComboBenchSize4); + DDX_Control(pDX, IDC_COMBO_BENCH_SIZE_5, m_ComboBenchSize5); + DDX_Control(pDX, IDC_COMBO_BENCH_SIZE_8, m_ComboBenchSize8); + + DDX_Control(pDX, IDC_COMBO_BENCH_QUEUE_0, m_ComboBenchQueues0); + DDX_Control(pDX, IDC_COMBO_BENCH_QUEUE_1, m_ComboBenchQueues1); + DDX_Control(pDX, IDC_COMBO_BENCH_QUEUE_2, m_ComboBenchQueues2); + DDX_Control(pDX, IDC_COMBO_BENCH_QUEUE_3, m_ComboBenchQueues3); + DDX_Control(pDX, IDC_COMBO_BENCH_QUEUE_4, m_ComboBenchQueues4); + DDX_Control(pDX, IDC_COMBO_BENCH_QUEUE_5, m_ComboBenchQueues5); + DDX_Control(pDX, IDC_COMBO_BENCH_QUEUE_8, m_ComboBenchQueues8); + + DDX_Control(pDX, IDC_COMBO_BENCH_THREAD_0, m_ComboBenchThreads0); + DDX_Control(pDX, IDC_COMBO_BENCH_THREAD_1, m_ComboBenchThreads1); + DDX_Control(pDX, IDC_COMBO_BENCH_THREAD_2, m_ComboBenchThreads2); + DDX_Control(pDX, IDC_COMBO_BENCH_THREAD_3, m_ComboBenchThreads3); + DDX_Control(pDX, IDC_COMBO_BENCH_THREAD_4, m_ComboBenchThreads4); + DDX_Control(pDX, IDC_COMBO_BENCH_THREAD_5, m_ComboBenchThreads5); + DDX_Control(pDX, IDC_COMBO_BENCH_THREAD_8, m_ComboBenchThreads8); + +// DDX_Control(pDX, IDC_COMBO_DATA, m_ComboData); +// DDX_Control(pDX, IDC_COMBO_AFFINITY, m_ComboAffinity); + DDX_Control(pDX, IDC_COMBO_MEASURE_TIME, m_ComboMeasureTime); + DDX_Control(pDX, IDC_COMBO_INTERVAL_TIME, m_ComboIntervalTime); + DDX_Control(pDX, IDC_OK, m_ButtonOk); +} + +BEGIN_MESSAGE_MAP(CSettingsDlg, CDialogFx) + ON_BN_CLICKED(IDC_SET_DEFAULT, &CSettingsDlg::OnSetDefault) + ON_BN_CLICKED(IDC_SET_NVME_8, &CSettingsDlg::OnSetNVMe8) + ON_BN_CLICKED(IDC_SET_FLASH_MEMORY, &CSettingsDlg::OnSetFlashMemory) + ON_BN_CLICKED(IDC_OK, &CSettingsDlg::OnOk) +END_MESSAGE_MAP() + +void CSettingsDlg::OnSetDefault() +{ + int type[9] = { 0, 0, 1, 1, 0, 1, 0, 1, 0 }; + int size[9] = { 1024, 1024, 4, 4, 1024, 4, 1024, 4, 1024 }; + int queues[9] = { 8, 1, 32, 1, 8, 32, 1, 1, 8 }; + int threads[9] = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + + for (int i = 0; i < 9; i++) + { + m_BenchType[i] = type[i]; + m_BenchSize[i] = size[i]; + m_BenchQueues[i] = queues[i]; + m_BenchThreads[i] = threads[i]; + } + + m_TestData = 0; + m_MeasureTime = 5; + m_IntervalTime = 5; + InitComboBox(); +} + +void CSettingsDlg::OnSetNVMe8() +{ + int type[9] = { 0, 0, 1, 1, 0, 1, 0, 1, 0 }; + int size[9] = { 1024, 128, 4, 4, 1024, 4, 1024, 4, 1024 }; + int queues[9] = { 8, 32, 32, 1, 8, 32, 1, 1, 8 }; + int threads[9] = { 1, 1, 16, 1, 1, 16, 1, 1, 1 }; + + for (int i = 0; i < 9; i++) + { + m_BenchType[i] = type[i]; + m_BenchSize[i] = size[i]; + m_BenchQueues[i] = queues[i]; + m_BenchThreads[i] = threads[i]; + } + + m_TestData = 0; + m_MeasureTime = 5; + m_IntervalTime = 5; + + InitComboBox(); +} + +void CSettingsDlg::OnSetFlashMemory() +{ + int type[9] = { 0, 0, 1, 1, 0, 1, 0, 1, 0 }; + int size[9] = { 1024, 1024, 4, 4, 1024, 4, 1024, 4, 1024 }; + int queues[9] = { 8, 1, 32, 1, 8, 32, 1, 1, 8 }; + int threads[9] = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + + for (int i = 0; i < 9; i++) + { + m_BenchType[i] = type[i]; + m_BenchSize[i] = size[i]; + m_BenchQueues[i] = queues[i]; + m_BenchThreads[i] = threads[i]; + } + + m_TestData = 0; + m_MeasureTime = 1; + m_IntervalTime = 30; + + InitComboBox(); +} + +BOOL CSettingsDlg::OnInitDialog() +{ + CDialogFx::OnInitDialog(); + + CString cstr; + + int type[9] = { 0, 0, 1, 1, 0, 1, 0, 1, 0 }; + int size[9] = { 1024, 1024, 4, 4, 1024, 4, 1024, 4, 1024 }; + int queues[9] = { 8, 1, 32, 1, 8, 32, 1, 1, 8 }; + int threads[9] = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + + for (int i = 0; i < 9; i++) + { + cstr.Format(L"BenchType%d", i); + m_BenchType[i] = GetPrivateProfileInt(L"Setting", cstr, type[i], m_Ini); + if (m_BenchType[i] < 0 || m_BenchSize[i] > 1) { m_BenchSize[i] = type[i]; } + + cstr.Format(L"BenchSize%d", i); + m_BenchSize[i] = GetPrivateProfileInt(L"Setting", cstr, size[i], m_Ini); + if (m_BenchSize[i] <= 0 || m_BenchSize[i] > 8192) { m_BenchSize[i] = size[i]; } + + cstr.Format(L"BenchQueues%d", i); + m_BenchQueues[i] = GetPrivateProfileInt(L"Setting", cstr, queues[i], m_Ini); + if (m_BenchQueues[i] <= 0 || m_BenchQueues[i] > MAX_QUEUES) { m_BenchQueues[i] = queues[i]; } + + cstr.Format(L"BenchThreads%d", i); + m_BenchThreads[i] = GetPrivateProfileInt(L"Setting", cstr, threads[i], m_Ini); + if (m_BenchThreads[i] <= 0 || m_BenchThreads[i] > MAX_THREADS) { m_BenchThreads[i] = threads[i]; } + } + + m_TestData = GetPrivateProfileInt(L"Setting", L"TestData", 0, m_Ini); + if (m_TestData < 0 || m_TestData > 1) + { + m_TestData = 0; + } + + InitComboBox(); + + m_LabelType.SetWindowTextW(i18n(L"Dialog", L"TYPE")); + m_LabelSize.SetWindowTextW(i18n(L"Dialog", L"BLOCK_SIZE")); + m_LabelQueues.SetWindowTextW(i18n(L"Dialog", L"QUEUES")); + m_LabelThreads.SetWindowTextW(i18n(L"Dialog", L"THREADS")); + m_LabelDefault.SetWindowTextW(L" " + i18n(L"Dialog", L"PROFILE_DEFAULT")); + m_LabelPeak.SetWindowTextW(L" " + i18n(L"Dialog", L"PROFILE_PEAK_PERFORMANCE")); + m_LabelDemo.SetWindowTextW(L" " + i18n(L"Dialog", L"PROFILE_DEMO")); + m_LabelMeasureTime.SetWindowTextW(L" " + i18n(L"Dialog", L"MEASURE_TIME")); + m_LabelIntervalTime.SetWindowTextW(L" " + i18n(L"Dialog", L"INTERVAL_TIME")); + + m_ButtonSetDefault.SetWindowTextW(i18n(L"Dialog", L"DEFAULT")); + SetWindowTitle(i18n(L"WindowTitle", L"SETTINGS")); + + UpdateDialogSize(); + + return TRUE; +} + +void CSettingsDlg::InitComboBox() +{ + m_ComboBenchType0.ResetContent(); + m_ComboBenchType1.ResetContent(); + m_ComboBenchType2.ResetContent(); + m_ComboBenchType3.ResetContent(); + m_ComboBenchType4.ResetContent(); + m_ComboBenchType5.ResetContent(); + m_ComboBenchType8.ResetContent(); + + for (int i = 0; i < 2; i++) + { + CString cstr; + if (i == 0) + { + cstr.Format(L"SEQ"); + } + else + { + cstr.Format(L"RND"); + } + m_ComboBenchType0.AddString(cstr); if (m_BenchType[0] == i) { m_ComboBenchType0.SetCurSel(i); } + m_ComboBenchType1.AddString(cstr); if (m_BenchType[1] == i) { m_ComboBenchType1.SetCurSel(i); } + m_ComboBenchType2.AddString(cstr); if (m_BenchType[2] == i) { m_ComboBenchType2.SetCurSel(i); } + m_ComboBenchType3.AddString(cstr); if (m_BenchType[3] == i) { m_ComboBenchType3.SetCurSel(i); } + m_ComboBenchType4.AddString(cstr); if (m_BenchType[4] == i) { m_ComboBenchType4.SetCurSel(i); } + m_ComboBenchType5.AddString(cstr); if (m_BenchType[5] == i) { m_ComboBenchType5.SetCurSel(i); } + m_ComboBenchType8.AddString(cstr); if (m_BenchType[8] == i) { m_ComboBenchType8.SetCurSel(i); } + } + + m_ComboBenchSize0.ResetContent(); + m_ComboBenchSize1.ResetContent(); + m_ComboBenchSize2.ResetContent(); + m_ComboBenchSize3.ResetContent(); + m_ComboBenchSize4.ResetContent(); + m_ComboBenchSize5.ResetContent(); + m_ComboBenchSize8.ResetContent(); + + int blockSize[] = { 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192 }; + for (int i = 0; i < 12; i++) + { + CString cstr; + if (blockSize[i] >= 1024) + { + cstr.Format(L"%dMiB", blockSize[i] / 1024); + } + else + { + cstr.Format(L"%dKiB", blockSize[i]); + } + m_ComboBenchSize0.AddString(cstr); if (m_BenchSize[0] == blockSize[i]) { m_ComboBenchSize0.SetCurSel(i); } + m_ComboBenchSize1.AddString(cstr); if (m_BenchSize[1] == blockSize[i]) { m_ComboBenchSize1.SetCurSel(i); } + m_ComboBenchSize2.AddString(cstr); if (m_BenchSize[2] == blockSize[i]) { m_ComboBenchSize2.SetCurSel(i); } + m_ComboBenchSize3.AddString(cstr); if (m_BenchSize[3] == blockSize[i]) { m_ComboBenchSize3.SetCurSel(i); } + m_ComboBenchSize4.AddString(cstr); if (m_BenchSize[4] == blockSize[i]) { m_ComboBenchSize4.SetCurSel(i); } + m_ComboBenchSize5.AddString(cstr); if (m_BenchSize[5] == blockSize[i]) { m_ComboBenchSize5.SetCurSel(i); } + m_ComboBenchSize8.AddString(cstr); if (m_BenchSize[8] == blockSize[i]) { m_ComboBenchSize8.SetCurSel(i); } + } + + // Queues + m_ComboBenchQueues0.ResetContent(); + m_ComboBenchQueues1.ResetContent(); + m_ComboBenchQueues2.ResetContent(); + m_ComboBenchQueues3.ResetContent(); + m_ComboBenchQueues4.ResetContent(); + m_ComboBenchQueues5.ResetContent(); + m_ComboBenchQueues8.ResetContent(); + + int queues[10] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 }; + // Sequential + for (int i = 0; i < 10; i++) + { + CString cstr; + cstr.Format(L"%d", queues[i]); + m_ComboBenchQueues0.AddString(cstr); if (m_BenchQueues[0] == queues[i]) { m_ComboBenchQueues0.SetCurSel(i); } + m_ComboBenchQueues1.AddString(cstr); if (m_BenchQueues[1] == queues[i]) { m_ComboBenchQueues1.SetCurSel(i); } + m_ComboBenchQueues2.AddString(cstr); if (m_BenchQueues[2] == queues[i]) { m_ComboBenchQueues2.SetCurSel(i); } + m_ComboBenchQueues3.AddString(cstr); if (m_BenchQueues[3] == queues[i]) { m_ComboBenchQueues3.SetCurSel(i); } + m_ComboBenchQueues4.AddString(cstr); if (m_BenchQueues[4] == queues[i]) { m_ComboBenchQueues4.SetCurSel(i); } + m_ComboBenchQueues5.AddString(cstr); if (m_BenchQueues[5] == queues[i]) { m_ComboBenchQueues5.SetCurSel(i); } + m_ComboBenchQueues8.AddString(cstr); if (m_BenchQueues[8] == queues[i]) { m_ComboBenchQueues8.SetCurSel(i); } + } + + // Threads + m_ComboBenchThreads0.ResetContent(); + m_ComboBenchThreads1.ResetContent(); + m_ComboBenchThreads2.ResetContent(); + m_ComboBenchThreads3.ResetContent(); + m_ComboBenchThreads4.ResetContent(); + m_ComboBenchThreads5.ResetContent(); + m_ComboBenchThreads8.ResetContent(); + + for (int i = 1; i <= 64; i++) + { + CString cstr; + cstr.Format(L"%d", i); + m_ComboBenchThreads0.AddString(cstr); if (m_BenchThreads[0] == i) { m_ComboBenchThreads0.SetCurSel(i - 1); } + m_ComboBenchThreads1.AddString(cstr); if (m_BenchThreads[1] == i) { m_ComboBenchThreads1.SetCurSel(i - 1); } + m_ComboBenchThreads2.AddString(cstr); if (m_BenchThreads[2] == i) { m_ComboBenchThreads2.SetCurSel(i - 1); } + m_ComboBenchThreads3.AddString(cstr); if (m_BenchThreads[3] == i) { m_ComboBenchThreads3.SetCurSel(i - 1); } + m_ComboBenchThreads4.AddString(cstr); if (m_BenchThreads[4] == i) { m_ComboBenchThreads4.SetCurSel(i - 1); } + m_ComboBenchThreads5.AddString(cstr); if (m_BenchThreads[5] == i) { m_ComboBenchThreads5.SetCurSel(i - 1); } + m_ComboBenchThreads8.AddString(cstr); if (m_BenchThreads[8] == i) { m_ComboBenchThreads8.SetCurSel(i - 1); } + } + + m_ComboMeasureTime.ResetContent(); + int measureTimes[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 60 }; + for (int i = 0; i < 13; i++) + { + CString cstr; + cstr.Format(L"%d", measureTimes[i]); + m_ComboMeasureTime.AddString(cstr); if (m_MeasureTime == measureTimes[i]) { m_ComboMeasureTime.SetCurSel(i); } + } + + m_ComboIntervalTime.ResetContent(); + int intervalTimes[] = { 0, 1, 3, 5, 10, 30, 60, 180, 300, 600 }; + for (int i = 0; i < 10; i++) + { + CString cstr; + cstr.Format(L"%d", intervalTimes[i]); + m_ComboIntervalTime.AddString(cstr); if (m_IntervalTime == intervalTimes[i]) { m_ComboIntervalTime.SetCurSel(i); } + } +} + +int CSettingsDlg::GetType(CString text) +{ + if (text.FindOneOf(L"SEQ") != -1) + { + return 0; + } + else + { + return 1; + } +} + +int CSettingsDlg::GetBlockSize(CString text) +{ + if(text.FindOneOf(L"M") != -1) + { + return _wtoi(text.GetString()) * 1024; + } + else + { + return _wtoi(text.GetString()); + } +} + +void CSettingsDlg::OnOk() +{ + CString cstr; + + m_ComboBenchType0.GetWindowTextW(cstr); cstr.Format(L"%d", GetType(cstr)); WritePrivateProfileString(L"Setting", L"BenchType0", cstr, m_Ini); + m_ComboBenchType1.GetWindowTextW(cstr); cstr.Format(L"%d", GetType(cstr)); WritePrivateProfileString(L"Setting", L"BenchType1", cstr, m_Ini); + m_ComboBenchType2.GetWindowTextW(cstr); cstr.Format(L"%d", GetType(cstr)); WritePrivateProfileString(L"Setting", L"BenchType2", cstr, m_Ini); + m_ComboBenchType3.GetWindowTextW(cstr); cstr.Format(L"%d", GetType(cstr)); WritePrivateProfileString(L"Setting", L"BenchType3", cstr, m_Ini); + m_ComboBenchType4.GetWindowTextW(cstr); cstr.Format(L"%d", GetType(cstr)); WritePrivateProfileString(L"Setting", L"BenchType4", cstr, m_Ini); + m_ComboBenchType5.GetWindowTextW(cstr); cstr.Format(L"%d", GetType(cstr)); WritePrivateProfileString(L"Setting", L"BenchType5", cstr, m_Ini); + m_ComboBenchType8.GetWindowTextW(cstr); cstr.Format(L"%d", GetType(cstr)); WritePrivateProfileString(L"Setting", L"BenchType8", cstr, m_Ini); + + m_ComboBenchSize0.GetWindowTextW(cstr); cstr.Format(L"%d", GetBlockSize(cstr)); WritePrivateProfileString(L"Setting", L"BenchSize0", cstr, m_Ini); + m_ComboBenchSize1.GetWindowTextW(cstr); cstr.Format(L"%d", GetBlockSize(cstr)); WritePrivateProfileString(L"Setting", L"BenchSize1", cstr, m_Ini); + m_ComboBenchSize2.GetWindowTextW(cstr); cstr.Format(L"%d", GetBlockSize(cstr)); WritePrivateProfileString(L"Setting", L"BenchSize2", cstr, m_Ini); + m_ComboBenchSize3.GetWindowTextW(cstr); cstr.Format(L"%d", GetBlockSize(cstr)); WritePrivateProfileString(L"Setting", L"BenchSize3", cstr, m_Ini); + m_ComboBenchSize4.GetWindowTextW(cstr); cstr.Format(L"%d", GetBlockSize(cstr)); WritePrivateProfileString(L"Setting", L"BenchSize4", cstr, m_Ini); + m_ComboBenchSize5.GetWindowTextW(cstr); cstr.Format(L"%d", GetBlockSize(cstr)); WritePrivateProfileString(L"Setting", L"BenchSize5", cstr, m_Ini); + m_ComboBenchSize8.GetWindowTextW(cstr); cstr.Format(L"%d", GetBlockSize(cstr)); WritePrivateProfileString(L"Setting", L"BenchSize8", cstr, m_Ini); + + m_ComboBenchQueues0.GetWindowTextW(cstr); WritePrivateProfileString(L"Setting", L"BenchQueues0", cstr, m_Ini); + m_ComboBenchQueues1.GetWindowTextW(cstr); WritePrivateProfileString(L"Setting", L"BenchQueues1", cstr, m_Ini); + m_ComboBenchQueues2.GetWindowTextW(cstr); WritePrivateProfileString(L"Setting", L"BenchQueues2", cstr, m_Ini); + m_ComboBenchQueues3.GetWindowTextW(cstr); WritePrivateProfileString(L"Setting", L"BenchQueues3", cstr, m_Ini); + m_ComboBenchQueues4.GetWindowTextW(cstr); WritePrivateProfileString(L"Setting", L"BenchQueues4", cstr, m_Ini); + m_ComboBenchQueues5.GetWindowTextW(cstr); WritePrivateProfileString(L"Setting", L"BenchQueues5", cstr, m_Ini); + m_ComboBenchQueues8.GetWindowTextW(cstr); WritePrivateProfileString(L"Setting", L"BenchQueues8", cstr, m_Ini); + + m_ComboBenchThreads0.GetWindowTextW(cstr); WritePrivateProfileString(L"Setting", L"BenchThreads0", cstr, m_Ini); + m_ComboBenchThreads1.GetWindowTextW(cstr); WritePrivateProfileString(L"Setting", L"BenchThreads1", cstr, m_Ini); + m_ComboBenchThreads2.GetWindowTextW(cstr); WritePrivateProfileString(L"Setting", L"BenchThreads2", cstr, m_Ini); + m_ComboBenchThreads3.GetWindowTextW(cstr); WritePrivateProfileString(L"Setting", L"BenchThreads3", cstr, m_Ini); + m_ComboBenchThreads4.GetWindowTextW(cstr); WritePrivateProfileString(L"Setting", L"BenchThreads4", cstr, m_Ini); + m_ComboBenchThreads5.GetWindowTextW(cstr); WritePrivateProfileString(L"Setting", L"BenchThreads5", cstr, m_Ini); + m_ComboBenchThreads8.GetWindowTextW(cstr); WritePrivateProfileString(L"Setting", L"BenchThreads8", cstr, m_Ini); + + m_ComboMeasureTime.GetWindowTextW(cstr); WritePrivateProfileString(L"Setting", L"MeasureTime", cstr, m_Ini); + m_ComboIntervalTime.GetWindowTextW(cstr); WritePrivateProfileString(L"Setting", L"IntervalTime", cstr, m_Ini); + + CDialogFx::OnCancel(); +} + +void CSettingsDlg::OnCancel() +{ + CDialogFx::OnCancel(); +} + +void CSettingsDlg::UpdateDialogSize() +{ + CDialogFx::UpdateDialogSize(); + + ChangeZoomType(m_ZoomType); + SetClientSize(SIZE_X, SIZE_Y, m_ZoomRatio); + UpdateBackground(FALSE, m_bDarkMode); + + COLORREF textColor = RGB(0, 0, 0); + COLORREF textSelectedColor = RGB(0, 0, 0); + +#ifdef SUISHO_SHIZUKU_SUPPORT + int fontSize = 16; + int comboHeight = 24; +#else + int fontSize = 12; + int comboHeight = 20; +#endif + + m_LabelType.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, RGB(0, 0, 0), FW_NORMAL, m_FontRender); + m_LabelSize.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, RGB(0, 0, 0), FW_NORMAL, m_FontRender); + m_LabelQueues.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, RGB(0, 0, 0), FW_NORMAL, m_FontRender); + m_LabelThreads.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, RGB(0, 0, 0), FW_NORMAL, m_FontRender); + m_LabelDefault.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, RGB(0, 0, 0), FW_NORMAL, m_FontRender); + m_LabelPeak.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, RGB(0, 0, 0), FW_NORMAL, m_FontRender); + m_LabelDemo.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, RGB(0, 0, 0), FW_NORMAL, m_FontRender); + m_LabelMeasureTime.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, RGB(0, 0, 0), FW_NORMAL, m_FontRender); + m_LabelIntervalTime.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, RGB(0, 0, 0), FW_NORMAL, m_FontRender); +// m_LabelAffinity.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, RGB(0, 0, 0), FW_NORMAL, m_FontRender); +// m_LabelData.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, RGB(0, 0, 0), FW_NORMAL, m_FontRender); + + m_ComboBenchType0.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchType1.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchType2.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchType3.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchType4.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchType5.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchType8.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + + m_ComboBenchSize0.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchSize1.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchSize2.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchSize3.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchSize4.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchSize5.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchSize8.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + + m_ComboBenchQueues0.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchQueues1.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchQueues2.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchQueues3.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchQueues4.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchQueues5.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchQueues8.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + + m_ComboBenchThreads0.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchThreads1.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchThreads2.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchThreads3.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchThreads4.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchThreads5.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboBenchThreads8.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + +// m_ComboData.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); +// m_ComboAffinity.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboMeasureTime.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ComboIntervalTime.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, textColor, textSelectedColor, FW_NORMAL, m_FontRender); + m_ButtonSetDefault.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, m_MeterText, FW_NORMAL, m_FontRender); + m_ButtonSetNVMe8.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, m_MeterText, FW_NORMAL, m_FontRender); + m_ButtonSetFlashMemory.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, m_MeterText, FW_NORMAL, m_FontRender); + m_ButtonOk.SetFontEx(m_FontFace, fontSize, fontSize, m_ZoomRatio, m_FontRatio, m_MeterText, FW_NORMAL, m_FontRender); + +#ifdef SUISHO_SHIZUKU_SUPPORT + m_LabelType.InitControl(8, 8, 160, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, FALSE); + m_LabelSize.InitControl(176, 8, 160, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, FALSE); + m_LabelQueues.InitControl(344, 8, 160, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, FALSE); + m_LabelThreads.InitControl(512, 8, 160, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, FALSE); + m_LabelDefault.InitControl(8, 32, 664, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); + m_LabelPeak.InitControl(8, 172, 664, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); + m_LabelDemo.InitControl(8, 256, 664, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); +// m_LabelData.InitControl(344, 316, 328, 24, m_ZoomRatio, &m_BkDC, NULL, 0, SS_LEFT, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); +// m_LabelAffinity.InitControl(8, 316, 328, 24, m_ZoomRatio, &m_BkDC, NULL, 0, SS_LEFT, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); + m_LabelMeasureTime.InitControl(8, 316, 328, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); + m_LabelIntervalTime.InitControl(344, 316, 328, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); + + m_ComboBenchType0.InitControl(8, 60, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchType1.InitControl(8, 88, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchType2.InitControl(8, 116, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchType3.InitControl(8, 144, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchType4.InitControl(8, 200, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchType5.InitControl(8, 228, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchType8.InitControl(8, 284, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + + m_ComboBenchSize0.InitControl(176, 60, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchSize1.InitControl(176, 88, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchSize2.InitControl(176, 116, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchSize3.InitControl(176, 144, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchSize4.InitControl(176, 200, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchSize5.InitControl(176, 228, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchSize8.InitControl(176, 284, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + + m_ComboBenchQueues0.InitControl(344, 60, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchQueues1.InitControl(344, 88, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchQueues2.InitControl(344, 116, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchQueues3.InitControl(344, 144, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchQueues4.InitControl(344, 200, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchQueues5.InitControl(344, 228, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchQueues8.InitControl(344, 284, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + + m_ComboBenchThreads0.InitControl(512, 60, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchThreads1.InitControl(512, 88, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchThreads2.InitControl(512, 116, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchThreads3.InitControl(512, 144, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchThreads4.InitControl(512, 200, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchThreads5.InitControl(512, 228, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchThreads8.InitControl(512, 284, 160, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + +// m_ComboData.InitControl(352, 344, 320, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); +// m_ComboAffinity.InitControl(16, 344, 320, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboMeasureTime.InitControl(8, 344, 328, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboIntervalTime.InitControl(344, 344, 328, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + + m_ButtonSetDefault.InitControl(8, 376, 160, 32, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); + m_ButtonSetNVMe8.InitControl(176, 376, 160, 32, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); + m_ButtonSetFlashMemory.InitControl(344, 376, 160, 32, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); + m_ButtonOk.InitControl(512, 376, 160, 32, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); +#else + m_LabelType.InitControl(8, 8, 100, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, FALSE); + m_LabelSize.InitControl(116, 8, 100, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, FALSE); + m_LabelQueues.InitControl(224, 8, 100, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, FALSE); + m_LabelThreads.InitControl(332, 8, 100, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, FALSE); + m_LabelDefault.InitControl(8, 28, 424, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); + m_LabelPeak.InitControl(8, 148, 424, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); + m_LabelDemo.InitControl(8, 220, 424, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); +// m_LabelAffinity.InitControl(8, 272, 208, 20, m_ZoomRatio, &m_BkDC, NULL, 0, SS_LEFT, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); +// m_LabelData.InitControl(224, 272, 208, 20, m_ZoomRatio, &m_BkDC, NULL, 0, SS_LEFT, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); + m_LabelMeasureTime.InitControl(8, 272, 208, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); + m_LabelIntervalTime.InitControl(224, 272, 208, 20, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, SS_LEFT, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); + + m_ComboBenchType0.InitControl(8, 52, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchType1.InitControl(8, 76, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchType2.InitControl(8, 100, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchType3.InitControl(8, 124, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchType4.InitControl(8, 172, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchType5.InitControl(8, 196, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchType8.InitControl(8, 244, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + + m_ComboBenchSize0.InitControl(116, 52, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchSize1.InitControl(116, 76, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchSize2.InitControl(116, 100, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchSize3.InitControl(116, 124, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchSize4.InitControl(116, 172, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchSize5.InitControl(116, 196, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchSize8.InitControl(116, 244, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + + m_ComboBenchQueues0.InitControl(224, 52, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchQueues1.InitControl(224, 76, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchQueues2.InitControl(224, 100, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchQueues3.InitControl(224, 124, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchQueues4.InitControl(224, 172, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchQueues5.InitControl(224, 196, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchQueues8.InitControl(224, 244, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + + m_ComboBenchThreads0.InitControl(332, 52, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchThreads1.InitControl(332, 76, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchThreads2.InitControl(332, 100, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchThreads3.InitControl(332, 124, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchThreads4.InitControl(332, 172, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchThreads5.InitControl(332, 196, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboBenchThreads8.InitControl(332, 244, 100, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + +// m_ComboData.InitControl(232, 296, 200, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); +// m_ComboAffinity.InitControl(16, 296, 200, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboMeasureTime.InitControl(8, 296, 208, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + m_ComboIntervalTime.InitControl(224, 296, 208, 200, m_ZoomRatio, &m_BkDC, NULL, 0, ES_LEFT, OwnerDrawTransparent, m_bHighContrast, m_bDarkMode, RGB(255, 255, 255), RGB(160, 220, 255), RGB(255, 255, 255), 0); + + m_ButtonSetDefault.InitControl(8, 324, 100, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); + m_ButtonSetNVMe8.InitControl(116, 324, 100, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); + m_ButtonSetFlashMemory.InitControl(224, 324, 100, 24, m_ZoomRatio, m_hPal,&m_BkDC, NULL, 0, BS_CENTER, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); + m_ButtonOk.InitControl(332, 324, 100, 24, m_ZoomRatio, m_hPal, &m_BkDC, NULL, 0, BS_CENTER, SystemDraw, m_bHighContrast, m_bDarkMode, FALSE); +#endif + +// m_ButtonSetDefault.SetDrawFrame(TRUE); +// m_ButtonSetNVMe8.SetDrawFrame(TRUE); +// m_ButtonSetNVMe9.SetDrawFrame(TRUE); +// m_ButtonOk.SetDrawFrame(TRUE); + + m_ButtonSetDefault.SetHandCursor(); + m_ButtonSetNVMe8.SetHandCursor(); + m_ButtonSetFlashMemory.SetHandCursor(); + m_ButtonOk.SetHandCursor(); + + SetDarkModeControl(m_ButtonOk.GetSafeHwnd(), m_bDarkMode); + SetDarkModeControl(m_ButtonSetDefault.GetSafeHwnd(), m_bDarkMode); + SetDarkModeControl(m_ButtonSetNVMe8.GetSafeHwnd(), m_bDarkMode); + SetDarkModeControl(m_ButtonSetFlashMemory.GetSafeHwnd(), m_bDarkMode); + + m_ComboBenchType0.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchType1.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchType2.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchType3.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchType4.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchType5.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchType8.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + + m_ComboBenchSize0.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchSize1.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchSize2.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchSize3.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchSize4.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchSize5.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchSize8.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + + m_ComboBenchQueues0.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchQueues1.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchQueues2.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchQueues3.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchQueues4.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchQueues5.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchQueues8.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + + m_ComboBenchThreads0.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchThreads1.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchThreads2.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchThreads3.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchThreads4.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchThreads5.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboBenchThreads8.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + +// m_ComboData.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); +// m_ComboAffinity.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboMeasureTime.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + m_ComboIntervalTime.SetItemHeightAll(comboHeight, m_ZoomRatio, m_FontRatio); + + m_ComboBenchType0.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchType1.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchType2.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchType3.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchType4.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchType5.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchType8.SetMargin(0, 4, 0, 0, m_ZoomRatio); + + m_ComboBenchSize0.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchSize1.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchSize2.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchSize3.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchSize4.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchSize5.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchSize8.SetMargin(0, 4, 0, 0, m_ZoomRatio); + + m_ComboBenchQueues0.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchQueues1.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchQueues2.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchQueues3.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchQueues4.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchQueues5.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchQueues8.SetMargin(0, 4, 0, 0, m_ZoomRatio); + + m_ComboBenchThreads0.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchThreads1.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchThreads2.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchThreads3.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchThreads4.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchThreads5.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboBenchThreads8.SetMargin(0, 4, 0, 0, m_ZoomRatio); + +// m_ComboData.SetMargin(0, 4, 0, 0, m_ZoomRatio); +// m_ComboAffinity.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboMeasureTime.SetMargin(0, 4, 0, 0, m_ZoomRatio); + m_ComboIntervalTime.SetMargin(0, 4, 0, 0, m_ZoomRatio); + + m_ComboBenchType4.EnableWindow(FALSE); + m_ComboBenchType5.EnableWindow(FALSE); + + Invalidate(); +} \ No newline at end of file diff --git a/CristalDiskMark/source/CrystalDiskMark/SettingsDlg.h b/CristalDiskMark/source/CrystalDiskMark/SettingsDlg.h new file mode 100644 index 0000000..7c09880 --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/SettingsDlg.h @@ -0,0 +1,108 @@ +/*---------------------------------------------------------------------------*/ +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : https://crystalmark.info/ +// License : MIT License +/*---------------------------------------------------------------------------*/ + +#pragma once + +#include "DialogFx.h" +#include "StaticFx.h" +#include "ButtonFx.h" +#include "ComboBoxFx.h" + +class CSettingsDlg : public CDialogFx +{ + DECLARE_DYNCREATE(CSettingsDlg) + +#ifdef SUISHO_SHIZUKU_SUPPORT + static const int SIZE_X = 680; + static const int SIZE_Y = 416; +#else + static const int SIZE_X = 440; + static const int SIZE_Y = 356; +#endif + +public: + CSettingsDlg(CWnd* pParent = NULL); + virtual ~CSettingsDlg(); + + enum { IDD = IDD_SETTINGS }; + +protected: + virtual void DoDataExchange(CDataExchange* pDX); + virtual BOOL OnInitDialog(); + virtual void OnCancel(); + + void UpdateDialogSize(); + int GetBlockSize(CString text); + int GetType(CString text); + + CStaticFx m_LabelType; + CStaticFx m_LabelSize; + CStaticFx m_LabelQueues; + CStaticFx m_LabelThreads; + CStaticFx m_LabelDefault; + CStaticFx m_LabelPeak; + CStaticFx m_LabelDemo; + CStaticFx m_LabelMeasureTime; + CStaticFx m_LabelIntervalTime; + + CComboBoxFx m_ComboBenchType0; + CComboBoxFx m_ComboBenchType1; + CComboBoxFx m_ComboBenchType2; + CComboBoxFx m_ComboBenchType3; + CComboBoxFx m_ComboBenchType4; + CComboBoxFx m_ComboBenchType5; + CComboBoxFx m_ComboBenchType8; + CComboBoxFx m_ComboBenchSize0; + CComboBoxFx m_ComboBenchSize1; + CComboBoxFx m_ComboBenchSize2; + CComboBoxFx m_ComboBenchSize3; + CComboBoxFx m_ComboBenchSize4; + CComboBoxFx m_ComboBenchSize5; + CComboBoxFx m_ComboBenchSize8; + CComboBoxFx m_ComboBenchQueues0; + CComboBoxFx m_ComboBenchQueues1; + CComboBoxFx m_ComboBenchQueues2; + CComboBoxFx m_ComboBenchQueues3; + CComboBoxFx m_ComboBenchQueues4; + CComboBoxFx m_ComboBenchQueues5; + CComboBoxFx m_ComboBenchQueues8; + CComboBoxFx m_ComboBenchThreads0; + CComboBoxFx m_ComboBenchThreads1; + CComboBoxFx m_ComboBenchThreads2; + CComboBoxFx m_ComboBenchThreads3; + CComboBoxFx m_ComboBenchThreads4; + CComboBoxFx m_ComboBenchThreads5; + CComboBoxFx m_ComboBenchThreads8; + +// CComboBoxFx m_ComboAffinity; +// CComboBoxFx m_ComboData; + CComboBoxFx m_ComboMeasureTime; + CComboBoxFx m_ComboIntervalTime; + + CButtonFx m_ButtonSetDefault; + CButtonFx m_ButtonSetNVMe8; + CButtonFx m_ButtonSetFlashMemory; + CButtonFx m_ButtonOk; + + void OnSetDefault(); + void OnSetNVMe8(); + void OnSetFlashMemory(); + void OnOk(); + void InitComboBox(); + + int m_BenchType[9]; + int m_BenchSize[9]; + int m_BenchQueues[9]; + int m_BenchThreads[9]; + + int m_TestData; + int m_MeasureTime; + int m_IntervalTime; + DWORD m_Profile; + + DECLARE_MESSAGE_MAP() +}; diff --git a/CristalDiskMark/source/CrystalDiskMark/res/DiskMark.ico b/CristalDiskMark/source/CrystalDiskMark/res/DiskMark.ico new file mode 100644 index 0000000..f9714b8 Binary files /dev/null and b/CristalDiskMark/source/CrystalDiskMark/res/DiskMark.ico differ diff --git a/CristalDiskMark/source/CrystalDiskMark/res/DiskMark.rc2 b/CristalDiskMark/source/CrystalDiskMark/res/DiskMark.rc2 new file mode 100644 index 0000000..a99eeaf --- /dev/null +++ b/CristalDiskMark/source/CrystalDiskMark/res/DiskMark.rc2 @@ -0,0 +1,13 @@ +// +// DiskMark.RC2 - Microsoft Visual C++ ŒڕҏWȂ\[X +// + +#ifdef APSTUDIO_INVOKED +#error ̃t@ĆAMicrosoft Visual C++ ŕҏWł܂B +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// 蓮ŕҏWꂽ\[Xɒlj܂... + +///////////////////////////////////////////////////////////////////////////// diff --git a/CristalDiskMark/source/CrystalDiskMark/res/DiskMark16.ico b/CristalDiskMark/source/CrystalDiskMark/res/DiskMark16.ico new file mode 100644 index 0000000..50c135b Binary files /dev/null and b/CristalDiskMark/source/CrystalDiskMark/res/DiskMark16.ico differ diff --git a/CristalDiskMark/source/CrystalDiskMark/res/DiskMarkA.ico b/CristalDiskMark/source/CrystalDiskMark/res/DiskMarkA.ico new file mode 100644 index 0000000..93fed3e Binary files /dev/null and b/CristalDiskMark/source/CrystalDiskMark/res/DiskMarkA.ico differ diff --git a/CristalDiskMark/source/CrystalDiskMark/res/DiskMarkA16.ico b/CristalDiskMark/source/CrystalDiskMark/res/DiskMarkA16.ico new file mode 100644 index 0000000..30f1f05 Binary files /dev/null and b/CristalDiskMark/source/CrystalDiskMark/res/DiskMarkA16.ico differ diff --git a/CristalDiskMark/source/CrystalDiskMark/res/DiskMarkS.ico b/CristalDiskMark/source/CrystalDiskMark/res/DiskMarkS.ico new file mode 100644 index 0000000..0b256ef Binary files /dev/null and b/CristalDiskMark/source/CrystalDiskMark/res/DiskMarkS.ico differ diff --git a/CristalDiskMark/source/CrystalDiskMark/res/DiskMarkS16.ico b/CristalDiskMark/source/CrystalDiskMark/res/DiskMarkS16.ico new file mode 100644 index 0000000..627ab9b Binary files /dev/null and b/CristalDiskMark/source/CrystalDiskMark/res/DiskMarkS16.ico differ diff --git a/CristalDiskMark/source/License/COPYRIGHT-ja.txt b/CristalDiskMark/source/License/COPYRIGHT-ja.txt new file mode 100644 index 0000000..0171b56 --- /dev/null +++ b/CristalDiskMark/source/License/COPYRIGHT-ja.txt @@ -0,0 +1,17 @@ +(C) 2007-2026 hiyohiyo + +ȉɒ߂ɏ]A{\tgEFAъ֘Ãt@Ciȉu\tgEF +Avj̕擾邷ׂĂ̐lɑ΂A\tgEFA𖳐ɈƂ𖳏ŋ +‚܂Bɂ́A\tgEFA̕gpAʁAύXAAfځAЕzATu +CZXA/܂͔̔錠Aу\tgEFA񋟂鑊ɓ +Ƃ‚錠Ɋ܂܂܂B + +L̒쌠\і{\A\tgEFÂׂĂ̕܂͏dvȕ +Lڂ̂Ƃ܂B + +\tgEFÁû܂܁vŁAł邩Öقł邩킸A̕ۏ؂ +񋟂܂Błۏ؂Ƃ́AiA̖ړIւ̓KAьN +Qɂ‚Ă̕ۏ؂܂݂܂AɌ肳̂ł͂܂B ҂܂͒ +쌠҂́A_sׁAs@sׁA܂͂ȊOł낤ƁA\tgEFAɋN܂ +֘AA邢̓\tgEFA̎gp܂͂̑̈ɂĐ؂̐A +QȂ̋`ɂ‚ĉ̐ӔCȂ̂Ƃ܂B diff --git a/CristalDiskMark/source/License/COPYRIGHT.txt b/CristalDiskMark/source/License/COPYRIGHT.txt new file mode 100644 index 0000000..b7e2fcd --- /dev/null +++ b/CristalDiskMark/source/License/COPYRIGHT.txt @@ -0,0 +1,19 @@ +(C) 2007-2026 hiyohiyo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/CristalDiskMark/source/License/DiskSpd-LICENSE.txt b/CristalDiskMark/source/License/DiskSpd-LICENSE.txt new file mode 100644 index 0000000..3cd6750 --- /dev/null +++ b/CristalDiskMark/source/License/DiskSpd-LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2014 Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/CristalDiskMark/source/License/PolyHook_2_0-LICENSE.txt b/CristalDiskMark/source/License/PolyHook_2_0-LICENSE.txt new file mode 100644 index 0000000..f76de84 --- /dev/null +++ b/CristalDiskMark/source/License/PolyHook_2_0-LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Stephen Eckels + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/CristalDiskMark/source/License/win32-custom-menubar-aero-theme-LICENSE.txt b/CristalDiskMark/source/License/win32-custom-menubar-aero-theme-LICENSE.txt new file mode 100644 index 0000000..c1f8899 --- /dev/null +++ b/CristalDiskMark/source/License/win32-custom-menubar-aero-theme-LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 adzm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/CristalDiskMark/source/License/win32-darkmode-LICENSE.txt b/CristalDiskMark/source/License/win32-darkmode-LICENSE.txt new file mode 100644 index 0000000..13bdb53 --- /dev/null +++ b/CristalDiskMark/source/License/win32-darkmode-LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Richard Yu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/CristalDiskMark/source/diskspd22/.gitattributes b/CristalDiskMark/source/diskspd22/.gitattributes new file mode 100644 index 0000000..df1344f --- /dev/null +++ b/CristalDiskMark/source/diskspd22/.gitattributes @@ -0,0 +1,26 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Use text conventions for commonly used text extensions. +*.csv text +*.ini text +*.json text +*.txt text +*.xml text +*.ps1 text +*.ps1xml text +*.psd1 text + + +# Denote all files that are truly binary and should not be modified. +*.dll binary +*.exe binary +*.gz binary +*.ico binary +*.jpg binary +*.lib binary +*.pdb binary +*.pdf binary +*.png binary +*.wim binary +*.zip binary diff --git a/CristalDiskMark/source/diskspd22/.gitignore b/CristalDiskMark/source/diskspd22/.gitignore new file mode 100644 index 0000000..f7af6c1 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/.gitignore @@ -0,0 +1,184 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Roslyn cache directories +*.ide/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +#NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +.vs/ +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding addin-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# If using the old MSBuild-Integrated Package Restore, uncomment this: +#!**/packages/repositories.config + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ diff --git a/CristalDiskMark/source/diskspd22/CmdLineParser/CmdLineParser.cpp b/CristalDiskMark/source/diskspd22/CmdLineParser/CmdLineParser.cpp new file mode 100644 index 0000000..cdf44ad --- /dev/null +++ b/CristalDiskMark/source/diskspd22/CmdLineParser/CmdLineParser.cpp @@ -0,0 +1,2080 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "CmdLineParser.h" +#include "Common.h" +#include "XmlProfileParser.h" +#include +#include +#include + +CmdLineParser::CmdLineParser() : + _dwBlockSize(64 * 1024), + _ulWriteRatio(0), + _hEventStarted(nullptr), + _hEventFinished(nullptr) +{ +} + +CmdLineParser::~CmdLineParser() +{ +} + +// Get size in bytes from a string (KMGTb) +bool CmdLineParser::_GetSizeInBytes(const char *pszSize, UINT64& ullSize, const char **pszRest) const +{ + bool fOk = true; + UINT64 ullResult = 0; + UINT64 ullMultiplier = 1; + const char *rest = nullptr; + + fOk = Util::ParseUInt(pszSize, ullResult, rest); + + if (fOk) + { + char ch = static_cast(toupper(*rest)); + + switch (ch) + { + case '\0': { break; } + case 'T': { ullMultiplier *= 1024; } + case 'G': { ullMultiplier *= 1024; } + case 'M': { ullMultiplier *= 1024; } + case 'K': { ullMultiplier *= 1024; ++rest; break; } + case 'B': { ullMultiplier = _dwBlockSize; ++rest; break; } + default: { + + // + // If caller is not expecting continuation, we know this is malformed now and + // can say so with respect to size specifiers. + // If there is continuation, caller is responsible for validating. + // + + if (!pszRest) + { + fOk = false; + fprintf(stderr, "Invalid size '%c'. Valid: K - KiB, M - MiB, G - GiB, T - TiB, B - block\n", *rest); + } + } + } + + if (fOk) + { + // + // Second chance after parsing valid size qualifier. + // + + if (!pszRest && *rest != '\0') + { + fOk = false; + fprintf(stderr, "Unrecognized characters after size specification\n"); + } + + // + // Now apply size specifier. + // + + else if (ullResult <= MAXUINT64 / ullMultiplier) + { + ullResult *= ullMultiplier; + } + else + { + // overflow + fOk = false; + fprintf(stderr, "Overflow applying multipler '%c'\n", ch); + } + } + } + else + { + fprintf(stderr, "Invalid integer\n"); + } + + if (fOk) + { + ullSize = ullResult; + + if (pszRest) + { + *pszRest = rest; + } + } + + return fOk; +} + +bool CmdLineParser::_GetRandomDataWriteBufferData(const string& sArg, UINT64& cb, string& sPath) +{ + bool fOk = true; + size_t iComma = sArg.find(','); + if (iComma == sArg.npos) + { + fOk = _GetSizeInBytes(sArg.c_str(), cb, nullptr); + sPath = ""; + } + else + { + fOk = _GetSizeInBytes(sArg.substr(0, iComma).c_str(), cb, nullptr); + sPath = sArg.substr(iComma + 1); + } + return fOk; +} + +void CmdLineParser::_DisplayUsageInfo(const char *pszFilename) const +{ + // ISSUE-REVIEW: this formats badly in the default 80 column command prompt + printf("\n"); + printf("Usage: %s [options] target1 [ target2 [ target3 ...] ]\n", pszFilename); + printf("version %s (%s)\n", DISKSPD_NUMERIC_VERSION_STRING, DISKSPD_DATE_VERSION_STRING); + printf("\n"); + + printf( + "Valid targets:\n" + " file_path\n" + " #\n" + " :\n" + "\n" + "Sizes, offsets and lengths are specified as integer bytes, or with an\n" + "optional suffix of KMGT (KiB/MiB/GiB/TiB) or b (for blocks, see -b).\n" + "Examples: 4k = 4096\n" + " with -b4k, 8b = 32768 (8 * 4KiB)\n" + "\n" + "Available options:\n" + " -? display usage information\n" + " -: experimental behaviors, as a bitmask of flags. current:\n" + " 1 - allow throughput rate limit sleeps >1ms if indicated by rate\n" + " -ag group affinity - threads assigned round-robin to CPUs by processor groups, 0 - n.\n" + " Groups are filled from lowest to highest processor before moving to the next.\n" + " [default; use -n to disable default affinity]\n" + " -a[g#,]#[,#,...]> advanced CPU affinity - threads assigned round-robin to the CPUs stated, in order of\n" + " specification; g# is the processor group for the following CPUs. If no group is\n" + " stated, 0 is default. Additional groups/processors can be added, comma separated,\n" + " on the same or separate -a parameters.\n" + " Examples: -a0,1,2 and -ag0,0,1,2 are equivalent.\n" + " -ag0,0,1,2,g1,0,1,2 specifies the first three CPUs in groups 0 and 1.\n" + " -ag0,0,1,2,g1,0,1,2 and -ag0,0,1,2 -ag1,0,1,2 are equivalent.\n" + " -b IO size, defines the block \'b\' for sizes stated in units of blocks [default=64K]\n" + " -B[:length] bounds; specify range of target to issue IO to - base offset and length\n" + " (default: IO is issued across the entire target)\n" + " -c create file targets of the given size. Conflicts with non-file target specifications.\n" + " -C cool down time - duration of the test after measurements finished [default=0s].\n" + " -D Capture IOPs statistics in intervals of ; these are per-thread\n" + " per-target: text output provides IOPs standard deviation, XML provides the full\n" + " IOPs time series in addition. [default=1000, 1 second].\n" + " -d duration (in seconds) to run test [default=10s]\n" + " -f maximum target offset to issue IO to (non-inclusive); -Bbase -f(base+length) is the same\n" + " as -Bbase:length. For example, to test only the first sectors of a disk.\n" + " -f open file with one or more additional access hints\n" + " r : the FILE_FLAG_RANDOM_ACCESS hint\n" + " s : the FILE_FLAG_SEQUENTIAL_SCAN hint\n" + " t : the FILE_ATTRIBUTE_TEMPORARY hint\n" + " [default: none]\n" + " -F total number of threads (conflicts with -t)\n" + " -g[i] throughput per-thread per-target throttled to given value; defaults to bytes per millisecond\n" + " With the optional i qualifier the value is IOPS of the specified block size (-b).\n" + " Throughput limits cannot be specified when using completion routines (-x)\n" + " [default: no limit]\n" + " -h deprecated, see -Sh\n" + " -i number of IOs per burst; see -j [default: inactive]\n" + " -j interval in between issuing IO bursts; see -i [default: inactive]\n" + " -I Set IO priority to . Available values are: 1-very low, 2-low, 3-normal (default)\n" + " -l Use large pages for IO buffers\n" + " -L measure latency statistics\n" + " -n disable default affinity (-a)\n" + " -N specify the flush mode for memory mapped I/O\n" + " v : uses the FlushViewOfFile API\n" + " n : uses the RtlFlushNonVolatileMemory API\n" + " i : uses RtlFlushNonVolatileMemory without waiting for the flush to drain\n" + " [default: none]\n" + " -o number of outstanding I/O requests per target per thread\n" + " (1=synchronous I/O, unless more than 1 thread is specified with -F)\n" + " [default=2]\n" + " -O number of outstanding I/O requests per thread - for use with -F\n" + " (1=synchronous I/O)\n" + " -p start parallel sequential I/O operations with the same offset\n" + " (ignored if -r is specified, makes sense only with -o2 or greater)\n" + " -P enable printing a progress dot after each [default=65536]\n" + " completed I/O operations, counted separately by each thread \n" + " -r[align] random I/O aligned to [align] byte offsets within the target range (overrides -s)\n" + " [default alignment=block size (-b)]\n" + " -rd[params] specify an non-uniform distribution for random IO in the target\n" + " [default uniformly random]\n" + " distributions: pct, abs\n" + " all: IO%% and %%Target/Size are cumulative. If the sum of IO%% is less than 100%% the\n" + " remainder is applied to the remainder of the target. An IO%% of 0 indicates a gap -\n" + " no IO will be issued to that range of the target.\n" + " pct : parameter is a combination of IO%%/%%Target separated by : (colon)\n" + " Example: -rdpct90/10:0/10:5/20 specifies 90%% of IO in 10%% of the target, no IO\n" + " next 10%%, 5%% IO in the next 20%% and the remaining 5%% of IO in the last 60%%\n" + " abs : parameter is a combination of IO%%/Target Size separated by : (colon)\n" + " If the actual target size is smaller than the distribution, the relative values of IO%%\n" + " for the valid elements define the effective distribution.\n" + " Example: -rdabs90/10G:0/10G:5/20G specifies 90%% of IO in 10GiB of the target, no IO\n" + " next 10GiB, 5%% IO in the next 20GiB and the remaining 5%% of IO in the remaining\n" + " capacity of the target. If the target is only 20G, the distribution truncates at\n" + " 90/10G:0:10G and all IO is directed to the first 10G (equivalent to -f10G).\n" + " -rs percentage of requests which should be issued randomly; -r is used to specify IO alignment.\n" + " Sequential IO runs are homogeneous when a mixed r/w ratio is specified (-w) and their lengths\n" + " follow a geometric distribution based on the percentage (chance of next IO being sequential).\n" + " -R[p] output format. With the p prefix, the input profile (command line or XML) is validated and\n" + " re-output in the specified format without running load, useful for checking or building\n" + " complex profiles.\n" + " [default: text]\n" + " -s[i][align] stride size of [align] bytes, alignment & offset between operations\n" + " [default=non-interlocked, default alignment=block size (-b)]\n" + " By default threads track independent sequential IO offsets starting at base offset of the target.\n" + " With multiple threads this results in threads overlapping their IOs - see -T to divide\n" + " them into multiple separate sequential streams on the target.\n" + " With the optional i qualifier (-si) threads interlock on a shared sequential offset.\n" + " Interlocked operations may introduce overhead but make it possible to issue a single\n" + " sequential stream to a target which responds faster than one thread can drive.\n" + " (ignored if -r specified, -si conflicts with -p, -rs and -T)\n" + " -S[bhmruw] control caching behavior [default: caching is enabled, no writethrough]\n" + " non-conflicting flags may be combined in any order; ex: -Sbw, -Suw, -Swu\n" + " -S equivalent to -Su\n" + " -Sb enable caching (default, explicitly stated)\n" + " -Sh equivalent -Suw\n" + " -Sm enable memory mapped I/O\n" + " -Su disable software caching, equivalent to FILE_FLAG_NO_BUFFERING\n" + " -Sr disable local caching, with remote sw caching enabled; only valid for remote filesystems\n" + " -Sw enable writethrough (no hardware write caching), equivalent to FILE_FLAG_WRITE_THROUGH or\n" + " non-temporal writes for memory mapped I/O (-Sm)\n" + " -t number of threads per target (conflicts with -F)\n" + " -T starting separation between I/O operations performed on the same target by different threads\n" + " [default=0] (starting offset = base target offset + (thread number * )\n" + " only applies to -s sequential IO with #threads > 1, conflicts with -r and -si\n" + " -v[s] verbose mode - with s, only provide additional summary statistics\n" + " -w percentage of write requests (-w and -w0 are equivalent and result in a read-only workload).\n" + " absence of this switch indicates 100%% reads\n" + " IMPORTANT: a write test will destroy existing data without a warning\n" + " -W warm up time - duration of the test before measurements start [default=5s]\n" + " -x use completion routines instead of I/O Completion Ports\n" + " -X use an XML file to configure the workload. Profile defaults for -W/d/C (durations) and -R/v/z\n" + " (output format, verbosity and random seed) may be overriden by direct specification.\n" + " Targets can be defined in XML profiles as template paths of the form * (*1, *2, ...).\n" + " When run, specify the paths to substitute for the template paths in order on the command line.\n" + " The first specified target is *1, second is *2, and so on.\n" + " Example: diskspd -d60 -Xprof.xml first.bin second.bin (prof.xml using *1 and *2, 60s run)\n" + " -z[seed] set random seed [with no -z, seed=0; with plain -z, seed is based on system run time]\n" + "\n" + "Write buffers:\n" + " -Z zero buffers used for write tests\n" + " -Zr per IO random buffers used for write tests - this incurrs additional run-time\n" + " overhead to create random content and shouln't be compared to results run\n" + " without -Zr\n" + " -Z use a buffer filled with random data as a source for write operations.\n" + " -Z, use a buffer filled with data from as a source for write operations.\n" + "\n" + " By default, write source buffers are filled with a repeating pattern (0, 1, 2, ..., 255, 0, 1, ...)\n" + "\n" + "Synchronization:\n" + " -ys signals event before starting the actual run (no warmup)\n" + " (creates a notification event if does not exist)\n" + " -yf signals event after the actual run finishes (no cooldown)\n" + " (creates a notification event if does not exist)\n" + " -yr waits on event before starting the run (including warmup)\n" + " (creates a notification event if does not exist)\n" + " -yp stops the run when event is set; CTRL+C is bound to this event\n" + " (creates a notification event if does not exist)\n" + " -ye sets event and quits\n" + "\n" + "Event Tracing:\n" + " -e Use query perf timer (qpc), cycle count, or system timer respectively.\n" + " [default = q, query perf timer (qpc)]\n" + " -ep use paged memory for the NT Kernel Logger [default=non-paged memory]\n" + " -ePROCESS process start & end\n" + " -eTHREAD thread start & end\n" + " -eIMAGE_LOAD image load\n" + " -eDISK_IO physical disk IO\n" + " -eMEMORY_PAGE_FAULTS all page faults\n" + " -eMEMORY_HARD_FAULTS hard faults only\n" + " -eNETWORK TCP/IP, UDP/IP send & receive\n" + " -eREGISTRY registry calls\n" + "\n\n"); + + printf("Examples:\n\n"); + printf("Create 8192KB file and run read test on it for 1 second:\n\n"); + printf(" %s -c8192K -d1 testfile.dat\n", pszFilename); + printf("\n"); + printf("Set block size to 4KB, create 2 threads per file, 32 overlapped (outstanding)\n"); + printf("I/O operations per thread, disable all caching mechanisms and run block-aligned random\n"); + printf("access read test lasting 10 seconds:\n\n"); + printf(" %s -b4K -t2 -r -o32 -d10 -Sh testfile.dat\n\n", pszFilename); + printf("Create two 1GB files, set block size to 4KB, create 2 threads per file, affinitize threads\n"); + printf("to CPUs 0 and 1 (each file will have threads affinitized to both CPUs) and run read test\n"); + printf("lasting 10 seconds:\n\n"); + printf(" %s -c1G -b4K -t2 -d10 -a0,1 testfile1.dat testfile2.dat\n", pszFilename); + + printf("\n"); +} + +bool CmdLineParser::_ParseETWParameter(const char *arg, Profile *pProfile) +{ + assert(nullptr != arg); + assert(0 != *arg); + + bool fOk = true; + pProfile->SetEtwEnabled(true); + if (*(arg + 1) != '\0') + { + const char *c = arg + 1; + if (*c == 'p') + { + pProfile->SetEtwUsePagedMemory(true); + } + else if (*c == 'q') + { + pProfile->SetEtwUsePerfTimer(true); + } + else if (*c == 's') + { + pProfile->SetEtwUseSystemTimer(true); //default + } + else if (*c == 'c') + { + pProfile->SetEtwUseCyclesCounter(true); + } + else if (strcmp(c, "PROCESS") == 0) //process start & end + { + pProfile->SetEtwProcess(true); + } + else if (strcmp(c, "THREAD") == 0) //thread start & end + { + pProfile->SetEtwThread(true); + } + else if (strcmp(c, "IMAGE_LOAD") == 0) //image load + { + pProfile->SetEtwImageLoad(true); + } + else if (strcmp(c, "DISK_IO") == 0) //physical disk IO + { + pProfile->SetEtwDiskIO(true); + } + else if (strcmp(c, "MEMORY_PAGE_FAULTS") == 0) //all page faults + { + pProfile->SetEtwMemoryPageFaults(true); + } + else if (strcmp(c, "MEMORY_HARD_FAULTS") == 0) //hard faults only + { + pProfile->SetEtwMemoryHardFaults(true); + } + else if (strcmp(c, "NETWORK") == 0) //tcpip send & receive + { + pProfile->SetEtwNetwork(true); + } + else if (strcmp(c, "REGISTRY") == 0) //registry calls + { + pProfile->SetEtwRegistry(true); + } + else + { + fOk = false; + } + } + else + { + fOk = false; + } + + return fOk; +} + +bool CmdLineParser::_ParseAffinity(const char *arg, TimeSpan *pTimeSpan) +{ + bool fOk = true; + + assert(nullptr != arg); + assert('\0' != *arg); + + const char *c = arg + 1; + + // -a and -ag are functionally equivalent; group-aware affinity. + // Note that group-aware affinity is default. + + // look for the -a simple case + if (*c == '\0') + { + return true; + } + + // look for the -ag simple case + if (*c == 'g') + { + // peek ahead, done? + if (*(c + 1) == '\0') + { + return true; + } + + // leave the parser at the g; this is the start of a group number + } + + // more complex affinity -ag0,0,1,2,g1,0,1,2,... OR -a0,1,2,.. + // n counts the -a prefix, the first parsed character is string index 2 + DWORD nGroup = 0, nNum = 0, n = 2; + bool fGroup = false, fNum = false; + while (*c != '\0') + { + if ((*c >= '0') && (*c <= '9')) + { + // accumulating a number + fNum = true; + nNum = 10 * nNum + (*c - '0'); + } + else if (*c == 'g') + { + // bad: ggggg + if (fGroup) + { + fOk = false; + } + + // now parsing a group number + fGroup = true; + } + else if (*c == ',') + { + // separator; if parsing group and have a number, now have the group + if (fGroup && fNum) + { + if (nNum > MAXWORD) + { + fprintf(stderr, "ERROR: group %u is out of range\n", nNum); + fOk = false; + } + else + { + nGroup = nNum; + nNum = 0; + fGroup = false; + } + } + // at a split but don't have a parsed number, error + else if (!fNum) + { + fOk = false; + } + // have a parsed CPU number + else + { + if (nNum > MAXBYTE) + { + fprintf(stderr, "ERROR: CPU %u is out of range\n", nNum); + fOk = false; + } + else + { + pTimeSpan->AddAffinityAssignment((WORD)nGroup, (BYTE)nNum); + nNum = 0; + fNum = false; + } + } + } + else + { + fOk = false; + } + + // bail out to error pretty print on error + if (!fOk) + { + break; + } + + c++; + n++; + } + + // if parsing a group or don't have a final number, error + if (fGroup || !fNum) + { + fOk = false; + } + + if (fOk && nNum > MAXBYTE) + { + fprintf(stderr, "ERROR: CPU %u is out of range\n", nNum); + fOk = false; + } + + if (!fOk) + { + // mid-parse error, show the point at which it occured + if (*c != '\0') { + fprintf(stderr, "ERROR: syntax error parsing affinity at highlighted character\n-%s\n", arg); + while (n-- > 0) + { + fprintf(stderr, " "); + } + fprintf(stderr, "^\n"); + } + else + { + fprintf(stderr, "ERROR: incomplete affinity specification\n"); + } + } + + if (fOk) + { + // fprintf(stderr, "FINAL parsed group %d CPU %d\n", nGroup, nNum); + pTimeSpan->AddAffinityAssignment((WORD)nGroup, (BYTE)nNum); + } + + return fOk; +} + +bool CmdLineParser::_ParseFlushParameter(const char *arg, MemoryMappedIoFlushMode *FlushMode) +{ + assert(nullptr != arg); + assert(0 != *arg); + + bool fOk = true; + if (*(arg + 1) != '\0') + { + const char *c = arg + 1; + if (_stricmp(c, "v") == 0) + { + *FlushMode = MemoryMappedIoFlushMode::ViewOfFile; + } + else if (_stricmp(c, "n") == 0) + { + *FlushMode = MemoryMappedIoFlushMode::NonVolatileMemory; + } + else if (_stricmp(c, "i") == 0) + { + *FlushMode = MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain; + } + else + { + fOk = false; + } + } + else + { + fOk = false; + } + return fOk; +} + +bool CmdLineParser::_ParseRandomDistribution(const char *arg, vector& vTargets) +{ + vector vOr; + DistributionType dType; + bool fOk = false; + UINT32 pctAcc = 0, pctCur; // accumulated/cur pct io + UINT64 targetAcc = 0, targetCur; // accumulated/cur target + + if (!strncmp(arg, "pct", 3)) + { + dType = DistributionType::Percent; + } + else if (strncmp(arg, "abs", 3)) + { + fprintf(stderr, "Unrecognized random distribution type\n"); + return false; + } + else + { + dType = DistributionType::Absolute; + } + + arg += 3; + + // + // Parse pairs of + // + // * pct: percentage/target percentage + // * abs: percentage/absolute range of target + // + // Ex: 90/10:5/5 => [0,90) -> [0, 10) :: [90, 95) -> [10, 15) + // a remainder of [95, 100) -> [15, 100) would be applied. + // + // Percentages are cumulative and successively define the span of + // the preceding definition. Absolute ranges are also cumulative: + // 10/1G:90/1G puts 90% of accesses in the second 1G range of the + // target. + // + // A single percentage can be 100 but is of limited value since it + // would only be valid as a single element distribution. + // + // Basic numeric validations are done here (similar to XSD for XML). + // Cross validation with other workload parameters (blocksize) and whole + // distribution validation is delayed to common code. + // + + while (true) + { + // Consume IO% integer + fOk = Util::ParseUInt(arg, pctCur, arg); + if (!fOk) + { + fprintf(stderr, "Invalid integer IO%%: must be > 0 and <= %u\n", 100 - pctAcc); + return false; + } + // hole is ok + else if (pctCur > 100) + { + fprintf(stderr, "Invalid IO%% %u: must be >= 0 and <= %u\n", pctCur, 100 - pctAcc); + return false; + } + + // Expect separator + if (*arg++ != '/') + { + fprintf(stderr, "Expected / separator after %u\n", pctCur); + return false; + } + + // Consume Target%/Absolute range integer + if (dType == DistributionType::Percent) + { + // Percent specification + fOk = Util::ParseUInt(arg, targetCur, arg); + if (!fOk) + { + fprintf(stderr, "Invalid integer Target%%: must be > 0 and <= %I64u\n", 100 - targetAcc); + return false; + } + // no hole + else if (targetCur == 0 || targetCur > 100) + { + fprintf(stderr, "Invalid Target%% %I64u: must be > 0 and <= %I64u\n", targetCur, 100 - targetAcc); + return false; + } + } + else + { + // Size specification + fOk = CmdLineParser::_GetSizeInBytes(arg, targetCur, &arg); + if (!fOk) + { + // error already emitted + return fOk; + } + + if (targetCur == 0) + { + fprintf(stderr, "Invalid zero length target range\n"); + return false; + } + } + + // Add range from [accumulator - accumulator + current) => ... + // Note that zero pctCur indicates a hole where no IO is desired - this is recorded + // for fidelity of display/profile but will never match on lookup, as intended. + vOr.emplace_back(pctAcc, pctCur, make_pair(targetAcc, targetCur)); + + // Now move accumulators for the next tuple/completion + pctAcc += pctCur; + targetAcc += targetCur; + + // Expect/consume separator for next tuple? + if (*arg == ':') + { + ++arg; + continue; + } + + // Done? + if (*arg == '\0') + { + break; + } + + fprintf(stderr, "Unexpected characters in specification '%s'\n", arg); + return false; + } + + // Apply to all targets + for (auto& t : vTargets) + { + t.SetDistributionRange(vOr, dType); + } + + return true; +} + +bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[], Profile *pProfile, struct Synchronization *synch, bool& fXMLProfile) +{ + int nParamCnt = argc - 1; + const char** args = argv + 1; + bool fError = false; + + TimeSpan timeSpan; + + // + // Pass 1 - determine parameter set type: cmdline specification or XML, and preparse targets/blocksize + // + + ParseState isXMLSet = ParseState::Unknown; + + ParseState isXMLResultFormat = ParseState::Unknown; + ParseState isProfileOnly = ParseState::Unknown; + ParseState isVerbose = ParseState::Unknown; + ParseState isVerboseStats = ParseState::Unknown; + ParseState isRandomSeed = ParseState::Unknown; + ParseState isWarmupTime = ParseState::Unknown; + ParseState isDurationTime = ParseState::Unknown; + ParseState isCooldownTime = ParseState::Unknown; + ParseState isExperimentFlags = ParseState::Unknown; + + ULONG randomSeedValue = 0; + ULONG warmupTime = 0; + ULONG durationTime = 0; + ULONG cooldownTime = 0; + ULONG experimentFlags = 0; + const char *xmlProfile = nullptr; + + // + // Find all target specifications. Note that this assumes all non-target + // parameters are single tokens; e.g. "-Hsomevalue" and never "-H somevalue". + // Targets follow parameter specifications. + // + + vector vTargets; + for (int i = 0, inTargets = false; i < nParamCnt; ++i) + { + if (!_IsSwitchChar(args[i][0])) + { + inTargets = true; + + Target target; + target.SetPath(args[i]); + vTargets.push_back(target); + } + else if (inTargets) + { + fprintf(stderr, "ERROR: parameters (%s) must come before targets on the command line\n", args[i]); + return false; + } + } + + // + // Find composable and dependent parameters as we resolve the parameter set. + // + + for (int i = 0; i < nParamCnt; ++i) + { + if (_IsSwitchChar(args[i][0])) + { + const char *arg = &args[i][2]; + + switch(args[i][1]) + { + + + case ':': + // Experiment flags + experimentFlags = atoi(arg); + isExperimentFlags = ParseState::True; + break; + + case 'A': /// CrystalDiskMark Process ID + extern DWORD pid; + pid = (DWORD)strtoul(arg + 1, NULL, 10); + break; + + case 'b': + + // Block size does not compose with XML profile spec + if (isXMLSet == ParseState::True) + { + fprintf(stderr, "ERROR: -b is not compatible with -X XML profile specification\n"); + return false; + } + else + { + UINT64 ullBlockSize; + if (_GetSizeInBytes(arg, ullBlockSize, nullptr) && ullBlockSize < MAXUINT32) + { + for (auto &i : vTargets) + { + i.SetBlockSizeInBytes((DWORD)ullBlockSize); + } + } + else + { + fprintf(stderr, "ERROR: invalid block size passed to -b\n"); + return false; + } + _dwBlockSize = (DWORD)ullBlockSize; + + isXMLSet = ParseState::False; + } + break; + + case 'C': + { + int c = atoi(arg); + if (c >= 0) + { + cooldownTime = c; + isCooldownTime = ParseState::True; + } + else + { + fprintf(stderr, "ERROR: invalid cooldown time (-C): '%s'\n", arg); + return false; + } + } + break; + + case 'd': + { + int c = atoi(arg); + if (c >= 0) + { + durationTime = c; + isDurationTime = ParseState::True; + } + else + { + fprintf(stderr, "ERROR: invalid measured duration time (-d): '%s'\n", arg); + return false; + } + } + break; + + case 'W': + { + int c = atoi(arg); + if (c >= 0) + { + warmupTime = c; + isWarmupTime = ParseState::True; + } + else + { + fprintf(stderr, "ERROR: invalid warmup time (-W): '%s'\n", arg); + return false; + } + } + break; + + case 'R': + + // re-output profile only (no run) + if ('p' == *arg) + { + isProfileOnly = ParseState::True; + ++arg; + } + + if ('\0' != *arg) + { + // Explicit results format + if (strcmp(arg, "xml") == 0) + { + isXMLResultFormat = ParseState::True; + } + else if (strcmp(arg, "text") != 0) + { + fprintf(stderr, "ERROR: invalid results format (-R): '%s'\n", arg); + return false; + } + else + { + isXMLResultFormat = ParseState::False; + } + } + else + { + // allow for -Rp shorthand for default profile-only format + if (isProfileOnly != ParseState::True) + { + fprintf(stderr, "ERROR: unspecified results format -R: use [p]\n"); + return false; + } + } + break; + + case 'v': + + if (*arg == '\0') + { + isVerbose = ParseState::True; + } + else if (*arg == 's' && *(arg+1) == '\0') // -vs + { + isVerboseStats = ParseState::True; + } + else + { + fprintf(stderr, "ERROR: invalid verbose mode (-v): '%s'\n", arg); + return false; + } + break; + + case 'X': + + if (isXMLSet == ParseState::Unknown) + { + isXMLSet = ParseState::True; + } + else + { + fprintf(stderr, "ERROR: multiple XML profiles specified (-X)\n"); + return false; + } + xmlProfile = arg; + break; + + case 'z': + { + char *endPtr = nullptr; + + if (*arg == '\0') + { + randomSeedValue = (ULONG) GetTickCount64(); + } + else + { + randomSeedValue = strtoul(arg, &endPtr, 10); + if (*endPtr != '\0') + { + fprintf(stderr, "ERROR: invalid random seed value '%s' specified - must be a valid 32 bit integer\n", arg); + return false; + } + } + + isRandomSeed = ParseState::True; + } + break; + + default: + // no other switches are valid in combination with -X + // if we've seen X, this means it is bad + // if not, we know it will not be X + if (isXMLSet == ParseState::True) + { + fprintf(stderr, "ERROR: invalid XML profile specification; parameter %s not compatible with -X\n", args[i]); + return false; + } + else + { + isXMLSet = ParseState::False; + } + } + } + } + + // XML profile? + if (isXMLSet == ParseState::True) + { + if (!_ReadParametersFromXmlFile(xmlProfile, pProfile, &vTargets)) + { + return false; + } + } + + // + // Apply profile common parameters - note that results format is unmodified if R not explicitly provided + // + + if (isXMLResultFormat == ParseState::True) + { + pProfile->SetResultsFormat(ResultsFormat::Xml); + } + else if (isXMLResultFormat == ParseState::False) + { + pProfile->SetResultsFormat(ResultsFormat::Text); + } + + if (isProfileOnly == ParseState::True) + { + pProfile->SetProfileOnly(true); + } + + if (isVerbose == ParseState::True) + { + pProfile->SetVerbose(true); + } + + if (isVerboseStats == ParseState::True) + { + pProfile->SetVerboseStats(true); + } + + if (isExperimentFlags == ParseState::True) + { + g_ExperimentFlags = experimentFlags; + } + + // + // Apply timespan common composable parameters + // + + if (isXMLSet == ParseState::True) + { + for (auto& ts : const_cast &>(pProfile->GetTimeSpans())) + { + if (isRandomSeed == ParseState::True) { ts.SetRandSeed(randomSeedValue); } + if (isWarmupTime == ParseState::True) { ts.SetWarmup(warmupTime); } + if (isDurationTime == ParseState::True) { ts.SetDuration(durationTime); } + if (isCooldownTime == ParseState::True) { ts.SetCooldown(cooldownTime); } + } + } + else + { + if (isRandomSeed == ParseState::True) { timeSpan.SetRandSeed(randomSeedValue); } + if (isWarmupTime == ParseState::True) { timeSpan.SetWarmup(warmupTime); } + if (isDurationTime == ParseState::True) { timeSpan.SetDuration(durationTime); } + if (isCooldownTime == ParseState::True) { timeSpan.SetCooldown(cooldownTime); } + } + + // Now done if XML profile + if (isXMLSet == ParseState::True) + { + fXMLProfile = true; + return true; + } + + // + // Parse full command line for profile + // + + // initial parse for cache/writethrough + // these are built up across the entire cmd line and applied at the end. + // this allows for conflicts to be thrown for mixed -h/-S as needed. + TargetCacheMode t = TargetCacheMode::Undefined; + WriteThroughMode w = WriteThroughMode::Undefined; + MemoryMappedIoMode m = MemoryMappedIoMode::Undefined; + MemoryMappedIoFlushMode f = MemoryMappedIoFlushMode::Undefined; + + // seen base/max target offset specification yet? + ParseState isMaxTargetOffset = ParseState::Unknown; + ParseState isBaseTargetOffset = ParseState::Unknown; + + bool bExit = false; + while (nParamCnt) + { + const char* arg = *args; + const char* const carg = arg; // save for error reporting, arg is modified during parse + + // Targets follow parameters on command line. If this is a target, we are done now. + if (!_IsSwitchChar(*arg)) + { + break; + } + + // skip switch character, provide length + ++arg; + const size_t argLen = strlen(arg); + + switch (*arg) + { + case '?': + _DisplayUsageInfo(argv[0]); + exit(0); + + case ':': // experiment flags + // handled during composable parameter evaluation + break; + + case 'A': /// CrystalDiskMark Process ID + extern DWORD pid; + pid = (DWORD)strtoul(arg + 1, NULL, 10); + break; + + case 'a': // affinity + //-a1,2,3,4 (assign threads to cpus 1,2,3,4 (round robin)) + if (!_ParseAffinity(arg, &timeSpan)) + { + fError = true; + } + break; + + case 'b': //block size + // handled during composable parameter evaluation + break; + + case 'B': //base target offset (offset from 0) and optional length + if (*(arg + 1) != '\0') + { + UINT64 base; + UINT64 len; + const char *rest; + + if (isBaseTargetOffset == ParseState::True) + { + fprintf(stderr, "ERROR: base target offset (-Bbase[:length]) can only be specified once\n"); + fError = true; + break; + } + + if (_GetSizeInBytes(arg + 1, base, &rest)) + { + for (auto &i : vTargets) + { + i.SetBaseFileOffsetInBytes(base); + } + + isBaseTargetOffset = ParseState::True; + } + else + { + fprintf(stderr, "ERROR: invalid base target offset passed to -B\n"); + fError = true; + break; + } + + if (rest && *rest != '\0') + { + if (*rest != ':') + { + fprintf(stderr, "ERROR: unexpected characters after -Bbase; use \':\' to separate -Bbase:length\n"); + fError = true; + break; + } + + if (*(++rest) == '\0') + { + fprintf(stderr, "ERROR: -Bbase:length - no length provided\n"); + fError = true; + break; + } + + if (isMaxTargetOffset == ParseState::True) + { + fprintf(stderr, "ERROR: maximum target offset (-Bbase:length or -fsize) can only be specified once\n"); + fError = true; + break; + } + + if (!_GetSizeInBytes(rest, len, nullptr) || base + len < base) + { + fprintf(stderr, "ERROR: invalid target length passed to -B\n"); + fError = true; + break; + } + + for (auto &i : vTargets) + { + i.SetMaxFileSize(base + len); + } + + isMaxTargetOffset = ParseState::True; + } + } + else + { + fError = true; + } + break; + + case 'c': //create file of the given size + if (*(arg + 1) != '\0') + { + UINT64 cb; + if (_GetSizeInBytes(arg + 1, cb, nullptr)) + { + for (auto &i : vTargets) + { + i.SetFileSize(cb); + i.SetCreateFile(true); + } + } + else + { + fprintf(stderr, "ERROR: invalid target size passed to -c\n"); + fError = true; + } + } + else + { + fError = true; + } + break; + + case 'C': //cool down time + // handled during composable parameter evaluation + break; + + case 'd': //duration + // handled during composable parameter evaluation + break; + + case 'D': //standard deviation + { + timeSpan.SetCalculateIopsStdDev(true); + + int x = atoi(arg + 1); + if (x > 0) + { + timeSpan.SetIoBucketDurationInMilliseconds(x); + } + } + break; + + case 'e': //etw + if (!_ParseETWParameter(arg, pProfile)) + { + fError = true; + } + break; + + case 'f': + if ('\0' == *(arg + 1)) + { + fError = true; + break; + } + + // max target offset form? + if (isdigit(*(arg + 1))) + { + UINT64 cb; + + if (isMaxTargetOffset == ParseState::True) + { + fprintf(stderr, "ERROR: maximum target offset (-Bbase:length or -fsize) can only be specified once\n"); + fError = true; + break; + } + + if (!_GetSizeInBytes(arg + 1, cb, nullptr)) + { + fprintf(stderr, "ERROR: invalid max target size passed to -f\n"); + fError = true; + break; + } + + for (auto &i : vTargets) + { + i.SetMaxFileSize(cb); + } + + isMaxTargetOffset = ParseState::True; + break; + } + + // while -frs (or -fsr) are generally conflicting intentions as far as + // the OS is concerned, do not enforce + while (*(++arg) != '\0') + { + switch (*arg) + { + case 'r': + for (auto &i : vTargets) + { + i.SetRandomAccessHint(true); + } + break; + case 's': + for (auto &i : vTargets) + { + i.SetSequentialScanHint(true); + } + break; + case 't': + for (auto &i : vTargets) + { + i.SetTemporaryFileHint(true); + } + break; + default: + fError = true; + break; + } + } + break; + + case 'F': //total number of threads + { + int c = atoi(arg + 1); + if (c > 0) + { + timeSpan.SetThreadCount(c); + } + else + { + fError = true; + } + } + break; + + case 'g': //throughput in bytes per millisecond (gNNN) OR iops (gNNNi) + { + // units? + bool isBpms = false; + if (isdigit(arg[argLen - 1])) + { + isBpms = true; + } + else if (arg[argLen - 1] != 'i') + { + // not IOPS, so its bad + fError = true; + } + + if (!fError) + { + int c = atoi(arg + 1); + if (c > 0) + { + for (auto &i : vTargets) + { + if (isBpms) + { + i.SetThroughput(c); + } + else + { + i.SetThroughputIOPS(c); + } + } + } + else + { + fError = true; + } + } + } + break; + + case 'h': // compat: disable os cache and set writethrough; now equivalent to -Sh + if (t == TargetCacheMode::Undefined && + w == WriteThroughMode::Undefined) + { + t = TargetCacheMode::DisableOSCache; + w = WriteThroughMode::On; + } + else + { + fprintf(stderr, "ERROR: -h conflicts with earlier specification of cache/writethrough\n"); + fError = true; + } + break; + + case 'i': //number of IOs to issue before think time + { + int c = atoi(arg + 1); + if (c > 0) + { + for (auto &i : vTargets) + { + i.SetBurstSize(c); + i.SetUseBurstSize(true); + } + } + else + { + fError = true; + } + } + break; + + case 'j': //time to wait between bursts of IOs + { + int c = atoi(arg + 1); + if (c > 0) + { + for (auto &i : vTargets) + { + i.SetThinkTime(c); + i.SetEnableThinkTime(true); + } + } + else + { + fError = true; + } + } + break; + + case 'I': //io priority + { + int x = atoi(arg + 1); + if (x > 0 && x < 4) + { + PRIORITY_HINT hint[] = { IoPriorityHintVeryLow, IoPriorityHintLow, IoPriorityHintNormal }; + for (auto &i : vTargets) + { + i.SetIOPriorityHint(hint[x - 1]); + } + } + else + { + fError = true; + } + } + break; + + case 'l': //large pages + for (auto &i : vTargets) + { + i.SetUseLargePages(true); + } + break; + + case 'L': //measure latency + timeSpan.SetMeasureLatency(true); + break; + + case 'n': //disable affinity (by default simple affinity is turned on) + timeSpan.SetDisableAffinity(true); + break; + + case 'N': + if (!_ParseFlushParameter(arg, &f)) + { + fError = true; + } + break; + + case 'o': //request count (1==synchronous) + { + int c = atoi(arg + 1); + if (c > 0) + { + for (auto &i : vTargets) + { + i.SetRequestCount(c); + } + } + else + { + fError = true; + } + } + break; + + case 'O': //total number of IOs/thread - for use with -F + { + int c = atoi(arg + 1); + if (c > 0) + { + timeSpan.SetRequestCount(c); + } + else + { + fError = true; + } + } + break; + + case 'p': //start async IO operations with the same offset + //makes sense only for -o2 and greater + for (auto &i : vTargets) + { + i.SetUseParallelAsyncIO(true); + } + break; + + case 'P': //show progress every x IO operations + { + int c = atoi(arg + 1); + if (c < 1) + { + c = 65536; + } + pProfile->SetProgress(c); + } + break; + + case 'r': //random access + { + // mixed random/sequential pct split? + if (*(arg + 1) == 's') + { + int c = 0; + + ++arg; + if (*(arg + 1) == '\0') + { + fprintf(stderr, "ERROR: no random percentage passed to -rs\n"); + fError = true; + } + else + { + c = atoi(arg + 1); + if (c <= 0 || c > 100) + { + fprintf(stderr, "ERROR: random percentage passed to -rs should be between 1 and 100\n"); + fError = true; + } + } + if (!fError) + { + for (auto &i : vTargets) + { + // if random ratio is unset and actual alignment is already specified, + // -s was used: don't allow this for clarity of intent + if (!i.GetRandomRatio() && + i.GetBlockAlignmentInBytes(true)) + { + fprintf(stderr, "ERROR: use -r to specify IO alignment when using mixed random/sequential IO (-rs)\n"); + fError = true; + break; + } + // if random ratio was already set to something other than 100% (-r) + // then -rs was specified multiple times: catch and block this + if (i.GetRandomRatio() && + i.GetRandomRatio() != 100) + { + fprintf(stderr, "ERROR: mixed random/sequential IO (-rs) specified multiple times\n"); + fError = true; + break; + } + // Note that -rs100 is the same as -r. It will not result in the element + // in the XML profile; we will still only emit/accept 1-99 there. + // + // Saying -rs0 (sequential) would create an ambiguity between that and -r[nnn]. Rather + // than bend the intepretation of -r[nnn] for the special case of -rs0 we will error + // it out in the bounds check above. + i.SetRandomRatio(c); + } + } + } + + // random distribution + + else if (*(arg + 1) == 'd') + { + // advance past the d + arg += 2; + + fError = !_ParseRandomDistribution(arg, vTargets); + } + + // random block alignment + // if mixed random/sequential not already specified, set to 100% + else + { + + UINT64 cb = _dwBlockSize; + if (*(arg + 1) != '\0') + { + if (!_GetSizeInBytes(arg + 1, cb, nullptr) || (cb == 0)) + { + fprintf(stderr, "ERROR: invalid alignment passed to -r\n"); + fError = true; + } + } + if (!fError) + { + for (auto &i : vTargets) + { + // Do not override -rs specification + if (!i.GetRandomRatio()) + { + i.SetRandomRatio(100); + } + // Multiple -rNN? + // Note that -rs100 -r[NN] will pass since -rs does not set alignment. + // We are only validating a single -rNN specification. + else if (i.GetRandomRatio() == 100 && + i.GetBlockAlignmentInBytes(true)) + { + fprintf(stderr, "ERROR: random IO (-r) specified multiple times\n"); + fError = true; + break; + } + // -s already set the alignment? + if (i.GetBlockAlignmentInBytes(true)) + { + fprintf(stderr, "ERROR: sequential IO (-s) conflicts with random IO (-r/-rs)\n"); + fError = true; + break; + } + i.SetBlockAlignmentInBytes(cb); + } + } + } + } + break; + + case 'R': // output profile/results format engine + // handled during composable parameter evaluation + break; + + case 's': //stride size + { + int idx = 1; + + if ('i' == *(arg + idx)) + { + // do interlocked sequential mode + // ISSUE-REVIEW: this does nothing if -r is specified + // ISSUE-REVIEW: this does nothing if -p is specified + // ISSUE-REVIEW: this does nothing if we are single-threaded + for (auto &i : vTargets) + { + i.SetUseInterlockedSequential(true); + } + + idx++; + } + + for (auto &i : vTargets) + { + // conflict -s with -rs/-s + if (i.GetRandomRatio()) + { + if (i.GetRandomRatio() == 100) { + fprintf(stderr, "ERROR: sequential IO (-s) conflicts with random IO (-r/-rs)\n"); + } + else + { + fprintf(stderr, "ERROR: use -r to specify IO alignment for -rs\n"); + } + fError = true; + break; + } + + // conflict with multiple -s + if (i.GetBlockAlignmentInBytes(true)) + { + fprintf(stderr, "ERROR: sequential IO (-s) specified multiple times\n"); + fError = true; + break; + } + } + + if (*(arg + idx) != '\0') + { + UINT64 cb; + // Note that we allow -s0, as unusual as that would be. + // The counter-case of -r0 is invalid and checked for. + if (_GetSizeInBytes(arg + idx, cb, nullptr)) + { + for (auto &i : vTargets) + { + i.SetBlockAlignmentInBytes(cb); + } + } + else + { + fprintf(stderr, "ERROR: invalid stride size passed to -s\n"); + fError = true; + } + } + else + { + // explicitly pass through the block size so that we can detect + // -rs/-s intent conflicts when attempting to set -rs + for (auto &i : vTargets) + { + i.SetBlockAlignmentInBytes(i.GetBlockSizeInBytes()); + } + } + } + break; + + case 'S': //control os/hw/remote caching and writethrough + { + // parse flags - it is an error to multiply specify either property, which + // can be detected simply by checking if we move one from !undefined. + // this also handles conflict cases. + int idx; + for (idx = 1; !fError && *(arg + idx) != '\0'; idx++) + { + switch (*(arg + idx)) + { + case 'b': + if (t == TargetCacheMode::Undefined) + { + t = TargetCacheMode::Cached; + } + else + { + fprintf(stderr, "ERROR: -Sb conflicts with earlier specification of cache mode\n"); + fError = true; + } + break; + case 'h': + if (t == TargetCacheMode::Undefined && + w == WriteThroughMode::Undefined && + m == MemoryMappedIoMode::Undefined) + { + t = TargetCacheMode::DisableOSCache; + w = WriteThroughMode::On; + } + else + { + fprintf(stderr, "ERROR: -Sh conflicts with earlier specification of cache/writethrough/memory mapped\n"); + fError = true; + } + break; + case 'm': + if (m == MemoryMappedIoMode::Undefined && + t != TargetCacheMode::DisableOSCache) + { + m = MemoryMappedIoMode::On; + } + else + { + fprintf(stderr, "ERROR: -Sm conflicts with earlier specification of memory mapped IO/unbuffered IO\n"); + fError = true; + } + break; + case 'r': + if (t == TargetCacheMode::Undefined) + { + t = TargetCacheMode::DisableLocalCache; + } + else + { + fprintf(stderr, "ERROR: -Sr conflicts with earlier specification of cache mode\n"); + fError = true; + } + break; + case 'u': + if (t == TargetCacheMode::Undefined && + m == MemoryMappedIoMode::Undefined) + { + t = TargetCacheMode::DisableOSCache; + } + else + { + fprintf(stderr, "ERROR: -Su conflicts with earlier specification of cache mode/memory mapped IO\n"); + fError = true; + } + break; + case 'w': + if (w == WriteThroughMode::Undefined) + { + w = WriteThroughMode::On; + } + else + { + fprintf(stderr, "ERROR -Sw conflicts with earlier specification of write through\n"); + fError = true; + } + break; + default: + fprintf(stderr, "ERROR: unrecognized option provided to -S\n"); + fError = true; + break; + } + } + + // bare -S, parse loop did not advance + if (!fError && idx == 1) + { + if (t == TargetCacheMode::Undefined && + m == MemoryMappedIoMode::Undefined) + { + t = TargetCacheMode::DisableOSCache; + } + else + { + fprintf(stderr, "ERROR: -S conflicts with earlier specification of cache mode\n"); + fError = true; + } + } + } + break; + + case 't': //number of threads per file + { + int c = atoi(arg + 1); + if (c > 0) + { + for (auto &i : vTargets) + { + i.SetThreadsPerFile(c); + } + } + else + { + fError = true; + } + } + break; + + case 'T': //offsets between threads reading the same file + { + UINT64 cb; + if (_GetSizeInBytes(arg + 1, cb, nullptr) && (cb > 0)) + { + for (auto &i : vTargets) + { + i.SetThreadStrideInBytes(cb); + } + } + else + { + fprintf(stderr, "ERROR: invalid offset passed to -T\n"); + fError = true; + } + } + break; + + case 'v': //verbose mode + // handled during composable parameter evaluation + break; + + case 'w': //write test [default=read] + { + int c = 0; + + if (*(arg + 1) == '\0') + { + fprintf(stderr, "ERROR: no write ratio passed to -w\n"); + fError = true; + } + else + { + c = atoi(arg + 1); + if (c < 0 || c > 100) + { + fprintf(stderr, "ERROR: write ratio passed to -w must be between 0 and 100 (percent)\n"); + fError = true; + } + } + if (!fError) + { + for (auto &i : vTargets) + { + i.SetWriteRatio(c); + } + } + } + break; + + case 'W': //warm up time + // handled during composable parameter evaluation + break; + + case 'x': //completion routines + timeSpan.SetCompletionRoutines(true); + break; + + case 'y': //external synchronization + switch (*(arg + 1)) + { + + case 's': + _hEventStarted = CreateEvent(NULL, TRUE, FALSE, arg + 2); + if (NULL == _hEventStarted) + { + fprintf(stderr, "Error creating/opening start notification event: '%s'\n", arg + 2); + exit(1); // TODO: this class shouldn't terminate the process + } + break; + + case 'f': + _hEventFinished = CreateEvent(NULL, TRUE, FALSE, arg + 2); + if (NULL == _hEventFinished) + { + fprintf(stderr, "Error creating/opening finish notification event: '%s'\n", arg + 2); + exit(1); // TODO: this class shouldn't terminate the process + } + break; + + case 'r': + synch->hStartEvent = CreateEvent(NULL, TRUE, FALSE, arg + 2); + if (NULL == synch->hStartEvent) + { + fprintf(stderr, "Error creating/opening wait-for-start event: '%s'\n", arg + 2); + exit(1); // TODO: this class shouldn't terminate the process + } + break; + + case 'p': + synch->hStopEvent = CreateEvent(NULL, TRUE, FALSE, arg + 2); + if (NULL == synch->hStopEvent) + { + fprintf(stderr, "Error creating/opening force-stop event: '%s'\n", arg + 2); + exit(1); // TODO: this class shouldn't terminate the process + } + break; + + case 'e': + { + HANDLE hEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, arg + 2); + if (NULL == hEvent) + { + fprintf(stderr, "Error opening event '%s'\n", arg + 2); + exit(1); // TODO: this class shouldn't terminate the process + } + if (!SetEvent(hEvent)) + { + fprintf(stderr, "Error setting event '%s'\n", arg + 2); + exit(1); // TODO: this class shouldn't terminate the process + } + CloseHandle(hEvent); + printf("Succesfully set event: '%s'\n", arg + 2); + bExit = true; + break; + } + + default: + fError = true; + } + + case 'z': //random seed + // handled during composable parameter evaluation + break; + + case 'Z': //zero write buffers + if (*(arg + 1) == '\0') + { + for (auto &i : vTargets) + { + i.SetZeroWriteBuffers(true); + } + } + else if (*(arg + 1) == 'r' && *(arg + 2) == '\0') + { + timeSpan.SetRandomWriteData(true); + } + else + { + UINT64 cb = 0; + string sPath; + if (_GetRandomDataWriteBufferData(string(arg + 1), cb, sPath) && (cb > 0)) + { + for (auto &i : vTargets) + { + i.SetRandomDataWriteBufferSize(cb); + i.SetRandomDataWriteBufferSourcePath(sPath); + } + } + else + { + fprintf(stderr, "ERROR: invalid size passed to -Z\n"); + fError = true; + } + } + break; + + default: + fprintf(stderr, "ERROR: invalid option: '%s'\n", carg); + return false; + } + + if (fError) + { + // note: original pointer to the cmdline argument, without parse movement + fprintf(stderr, "ERROR: incorrectly provided option: '%s'\n", carg); + return false; + } + + --nParamCnt; + ++args; + } + + // + // exit if a user specified an action which was already satisfied and doesn't require running test + // + if (bExit) + { + printf("Now exiting...\n"); + exit(1); // TODO: this class shouldn't terminate the process + } + + if (vTargets.size() < 1) + { + fprintf(stderr, "ERROR: need to provide at least one filename\n"); + return false; + } + + // apply resultant cache/writethrough/memory mapped io modes to the targets + for (auto &i : vTargets) + { + if (t != TargetCacheMode::Undefined) + { + i.SetCacheMode(t); + } + if (w != WriteThroughMode::Undefined) + { + i.SetWriteThroughMode(w); + } + if (m != MemoryMappedIoMode::Undefined) + { + i.SetMemoryMappedIoMode(m); + } + if (f != MemoryMappedIoFlushMode::Undefined) + { + i.SetMemoryMappedIoFlushMode(f); + } + } + + // ... and apply targets to the timespan + for (auto &i : vTargets) + { + timeSpan.AddTarget(i); + } + pProfile->AddTimeSpan(timeSpan); + + return true; +} + +bool CmdLineParser::_ReadParametersFromXmlFile(const char *pszPath, Profile *pProfile, vector *pvSubstTargets) +{ + XmlProfileParser parser; + + return parser.ParseFile(pszPath, pProfile, pvSubstTargets, NULL); +} + +bool CmdLineParser::ParseCmdLine(const int argc, const char *argv[], Profile *pProfile, struct Synchronization *synch, SystemInformation *pSystem) +{ + assert(nullptr != argv); + assert(nullptr != pProfile); + assert(NULL != synch); + + if (argc < 2) + { + _DisplayUsageInfo(argv[0]); + return false; + } + + string sCmdLine; + for (int i = 0; i < argc - 1; i++) + { + sCmdLine += argv[i]; + sCmdLine += ' '; + } + if (argc > 0) + { + sCmdLine += argv[argc - 1]; + } + pProfile->SetCmdLine(sCmdLine); + + bool fOk = true; + bool fXMLProfile = false; + + fOk = _ReadParametersFromCmdLine(argc, argv, pProfile, synch, fXMLProfile); + + // Check additional restrictions and conditions on the parsed profile. + // Note that on the current cmdline, all targets receive the same parameters + // so their mutual consistency only needs to be checked once. Do not check + // system consistency in profile-only operation (this is only required at + // execution time). + + if (fOk) + { + fOk = pProfile->Validate(!fXMLProfile, pProfile->GetProfileOnly() ? nullptr : pSystem); + } + + return fOk; +} diff --git a/CristalDiskMark/source/diskspd22/CmdRequestCreator/CmdRequestCreator.cpp b/CristalDiskMark/source/diskspd22/CmdRequestCreator/CmdRequestCreator.cpp new file mode 100644 index 0000000..96eaac6 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/CmdRequestCreator/CmdRequestCreator.cpp @@ -0,0 +1,230 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +// CmdRequestCreator.cpp : Defines the entry point for the console application. +// + +#include "CmdRequestCreator.h" +#include +#include +#include +#include "common.h" +#include "errors.h" +#include "CmdLineParser.h" +#include "XmlProfileParser.h" +#include "IORequestGenerator.h" +#include "ResultParser.h" +#include "XmlResultParser.h" + +/*****************************************************************************/ +// global variables +static HANDLE g_hAbortEvent = NULL; // handle to the 'abort' event + // it allows stopping I/O Request Generator in the middle of its work + // the results of its work will be passed to the Results Parser +static HANDLE g_hEventStarted = NULL; // event signalled to notify that the actual (measured) test is to be started +static HANDLE g_hEventFinished = NULL; // event signalled to notify that the actual test has finished + +/*****************************************************************************/ +BOOL WINAPI ctrlCRoutine(DWORD dwCtrlType) +{ + if( CTRL_C_EVENT == dwCtrlType ) + { + printf("\n*** Interrupted by Ctrl-C. Stopping I/O Request Generator. ***\n"); + if( !SetEvent(g_hAbortEvent) ) + { + fprintf(stderr, "Warning: Setting abort event failed (error code: %u)\n", GetLastError()); + } + SetConsoleCtrlHandler(ctrlCRoutine, FALSE); + + //indicate that the signal has been handled + return TRUE; + } + else + { + return FALSE; + } +} + +/*****************************************************************************/ +void TestStarted() +{ + if( (NULL != g_hEventStarted) && !SetEvent(g_hEventStarted) ) + { + fprintf(stderr, "Warning: Setting test start notification event failed (error code: %u)\n", GetLastError()); + } +} + +/*****************************************************************************/ +void TestFinished() +{ + if( (NULL != g_hEventFinished) && !SetEvent(g_hEventFinished) ) + { + fprintf(stderr, "Warning: Setting test finish notification event failed (error code: %u)\n", GetLastError()); + } +} + +/*****************************************************************************/ +/// for CrystalDiskMark +DWORD pid = 0; + +int __cdecl main(int argc, const char* argv[]) +{ + /// for CrystalDiskMark + int totalScore = 0; + double averageLatency = 0.0; + + // + // parse cmd line parameters + // + struct Synchronization synch; //sychronization structure + synch.ulStructSize = sizeof(synch); + synch.hStopEvent = NULL; + synch.hStartEvent = NULL; + + CmdLineParser cmdLineParser; + Profile profile; + if (!cmdLineParser.ParseCmdLine(argc, argv, &profile, &synch, &g_SystemInformation)) + { + return ERROR_PARSE_CMD_LINE; + } + + // Instantiate parsers + ResultParser resultParser; + XmlResultParser xmlResultParser; + IResultParser *pResultParser = nullptr; + if (profile.GetResultsFormat() == ResultsFormat::Xml) + { + pResultParser = &xmlResultParser; + } + else + { + pResultParser = &resultParser; + } + + // Profile only? If so, complete now. + if (profile.GetProfileOnly()) + { + string s = pResultParser->ParseProfile(profile); + printf("%s", s.c_str()); + return 0; + } + + synch.pfnCallbackTestStarted = TestStarted; + synch.pfnCallbackTestFinished = TestFinished; + + // + // create abort event if stop event is not explicitly provided by the user (otherwise use the stop event) + // + + if (NULL == synch.hStopEvent) + { + synch.hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if( NULL == synch.hStopEvent ) + { + fprintf(stderr, "Unable to create an abort event for CTRL+C\n"); + //FUTURE EXTENSION: change error code + return 1; + } + } + g_hAbortEvent = synch.hStopEvent; // set abort event to either stop event provided by user or the just created event + + // + // capture ctrl+c + // + if( !SetConsoleCtrlHandler(ctrlCRoutine, TRUE) ) + { + fprintf(stderr, "Unable to set CTRL+C routine\n"); + //FUTURE EXTENSION: change error code + return 1; + } + + TraceLoggingRegister(g_hEtwProvider); + + // + // call IO request generator + // + + IORequestGenerator ioGenerator; + /// for CrystalDiskMark + if (!ioGenerator.GenerateRequests(profile, *pResultParser, &synch, &totalScore, &averageLatency)) + // if (!ioGenerator.GenerateRequests(profile, *pResultParser, &synch)) + { + if (profile.GetResultsFormat() == ResultsFormat::Xml) + { + fprintf(stderr, "\n"); + } + + fprintf(stderr, "Error generating I/O requests\n"); + return 1; + } + + TraceLoggingUnregister(g_hEtwProvider); + + if( NULL != synch.hStartEvent ) + { + CloseHandle(synch.hStartEvent); + } + if( NULL != synch.hStopEvent ) + { + CloseHandle(synch.hStopEvent); + } + if( g_hEventStarted ) + { + CloseHandle(g_hEventStarted); + } + if( NULL != g_hEventFinished ) + { + CloseHandle(g_hEventFinished); + } + + /// for CrystalDiskMark + CHAR name[32]; + snprintf(name, 32, "CrystalDiskMark%08X", pid); + auto size = 8; + + HANDLE hSharedMemory = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, NULL, size, name); + if (hSharedMemory != NULL) + { + auto pMemory = (double*)MapViewOfFile(hSharedMemory, FILE_MAP_ALL_ACCESS, NULL, NULL, size); + if (pMemory != NULL) + { + *pMemory = averageLatency; + UnmapViewOfFile(pMemory); + CloseHandle(hSharedMemory); + } + } + printf("Score: %d\n", totalScore); + printf("averageLatency: %f\n", averageLatency); + //fprintf(stderr, "%f", averageLatency); + + return totalScore; + + + // return 0; +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/CmdRequestCreator/diskspd.rc b/CristalDiskMark/source/diskspd22/CmdRequestCreator/diskspd.rc new file mode 100644 index 0000000..bebe04b --- /dev/null +++ b/CristalDiskMark/source/diskspd22/CmdRequestCreator/diskspd.rc @@ -0,0 +1,19 @@ +#include +#include "Version.h" + +DISKSPD.XSD HTML "..\\XmlProfileParser\\diskspd.xsd" + +#include + +#define VER_FILETYPE VFT_APP +#define VER_FILESUBTYPE VFT2_UNKNOWN +#define VER_FILEDESCRIPTION_STR "DiskSpd Storage Performance Tool" +#define VER_INTERNALNAME_STR "diskspd.exe" + +#undef VER_PRODUCTVERSION +#define VER_PRODUCTVERSION DISKSPD_MAJOR,DISKSPD_MINOR,DISKSPD_BUILD,DISKSPD_QFE + +#undef VER_PRODUCTVERSION_STR +#define VER_PRODUCTVERSION_STR DISKSPD_NUMERIC_VERSION_STRING + +#include "common.ver" diff --git a/CristalDiskMark/source/diskspd22/Common/CmdLineParser.h b/CristalDiskMark/source/diskspd22/Common/CmdLineParser.h new file mode 100644 index 0000000..c1010d7 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Common/CmdLineParser.h @@ -0,0 +1,73 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#include "Common.h" + +namespace UnitTests { class CmdLineParserUnitTests; } + +class CmdLineParser +{ +public: + CmdLineParser(); + ~CmdLineParser(); + + bool ParseCmdLine(const int argc, const char *argv[], Profile *pProfile, struct Synchronization *synch, SystemInformation *pSystem = nullptr); + +private: + bool _ReadParametersFromCmdLine(const int argc, const char *argv[], Profile *pProfile, struct Synchronization *synch, bool& fXMLProfile); + bool _ReadParametersFromXmlFile(const char *pszPath, Profile *pProfile, vector *pvSubstTargets); + + bool _ParseETWParameter(const char *arg, Profile *pProfile); + bool _ParseFlushParameter(const char *arg, MemoryMappedIoFlushMode *FlushMode ); + bool _ParseAffinity(const char *arg, TimeSpan *pTimeSpan); + bool _ParseRandomDistribution(const char *arg, vector& vTargets); + + void _DisplayUsageInfo(const char *pszFilename) const; + bool _GetSizeInBytes(const char *pszSize, UINT64& ullSize, const char **pszRest) const; + bool _GetRandomDataWriteBufferData(const string& sArg, UINT64& cb, string& sPath); + + static bool _IsSwitchChar(const char c) { return (c == '/' || c == '-'); } + enum class ParseState { + Unknown, + True, + False, + Bad + }; + + DWORD _dwBlockSize; // block size; other parameters may be stated in blocks + // so the block size is needed to process them + + UINT32 _ulWriteRatio; // default percentage of write requests + + HANDLE _hEventStarted; // event signalled to notify that the actual (measured) test is to be started + HANDLE _hEventFinished; // event signalled to notify that the actual test has finished + + friend class UnitTests::CmdLineParserUnitTests; +}; diff --git a/CristalDiskMark/source/diskspd22/Common/CmdRequestCreator.h b/CristalDiskMark/source/diskspd22/Common/CmdRequestCreator.h new file mode 100644 index 0000000..f8d2937 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Common/CmdRequestCreator.h @@ -0,0 +1,38 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include +#include \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Common/Common.cpp b/CristalDiskMark/source/diskspd22/Common/Common.cpp new file mode 100644 index 0000000..997d580 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Common/Common.cpp @@ -0,0 +1,1273 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "Common.h" + +TRACELOGGING_DEFINE_PROVIDER(g_hEtwProvider, + "Microsoft-Windows-DiskSpd", // {CA13DB84-D0A9-5145-FCA4-468DA92FDC2D} + (0xca13db84, 0xd0a9, 0x5145, 0xfc, 0xa4, 0x46, 0x8d, 0xa9, 0x2f, 0xdc, 0x2d)); + +SystemInformation g_SystemInformation; +ULONG g_ExperimentFlags; + +UINT64 PerfTimer::GetTime() +{ + LARGE_INTEGER li; + QueryPerformanceCounter(&li); + return li.QuadPart; +} + +UINT64 PerfTimer::_GetPerfTimerFreq() +{ + LARGE_INTEGER li; + QueryPerformanceFrequency(&li); + return li.QuadPart; +} + +const UINT64 PerfTimer::TIMER_FREQ = _GetPerfTimerFreq(); + +double PerfTimer::PerfTimeToMicroseconds(const double perfTime) +{ + return perfTime / (TIMER_FREQ / 1000000.0); +} + +double PerfTimer::PerfTimeToMilliseconds(const double perfTime) +{ + return PerfTimeToMicroseconds(perfTime) / 1000; +} + +double PerfTimer::PerfTimeToSeconds(const double perfTime) +{ + return PerfTimeToMilliseconds(perfTime) / 1000; +} + +double PerfTimer::PerfTimeToMicroseconds(const UINT64 perfTime) +{ + return PerfTimeToMicroseconds(static_cast(perfTime)); +} + +double PerfTimer::PerfTimeToMilliseconds(const UINT64 perfTime) +{ + return PerfTimeToMilliseconds(static_cast(perfTime)); +} + +double PerfTimer::PerfTimeToSeconds(const UINT64 perfTime) +{ + return PerfTimeToSeconds(static_cast(perfTime)); +} + +UINT64 PerfTimer::MicrosecondsToPerfTime(const double microseconds) +{ + return static_cast(TIMER_FREQ * (microseconds / 1000000.0)); +} + +UINT64 PerfTimer::MillisecondsToPerfTime(const double milliseconds) +{ + return static_cast(TIMER_FREQ * (milliseconds / 1000.0)); +} + +UINT64 PerfTimer::SecondsToPerfTime(const double seconds) +{ + return static_cast(TIMER_FREQ * seconds); +} + +Random::Random(UINT64 ulSeed) +{ + UINT32 i; + + _ulState[0] = 0xf1ea5eed; + _ulState[1] = ulSeed; + _ulState[2] = ulSeed; + _ulState[3] = ulSeed; + + for (i = 0; i < 20; i++) { + Rand64(); + } +} + +void Random::RandBuffer(BYTE *pBuffer, UINT32 ulLength, bool fPseudoRandomOkay) +{ + UINT64 *pBuffer64; + UINT32 Remaining = (UINT32)(((ULONG_PTR)pBuffer) & 7); + UINT64 r1, r2, r3, r4, r5; + + // + // Align to 8 bytes + // + + if (Remaining != 0) { + r1 = Rand64(); + + while (Remaining != 0 && ulLength != 0) { + *pBuffer = (BYTE)(r1 & 0xFF); + r1 >>= 8; + pBuffer++; + ulLength--; + Remaining--; + } + } + + pBuffer64 = (UINT64*)pBuffer; + Remaining = ulLength / 8; + ulLength -= Remaining * 8; + pBuffer += Remaining * 8; + + if (fPseudoRandomOkay) { + + // + // Generate 5 random numbers and then mix them to produce + // 16 random (but correlated) numbers. We want to do 16 + // numbers at a time for optimal cache line alignment. + // Only do this if the caller is okay with numbers that + // aren't independent. A detailed analysis of the data + // could probably detect that the first 5 numbers determine + // the next 11. For most purposes this won't matter (for + // instance it's unlikely compression algorithms will be + // able to detect this and utilize it). + // + + while (Remaining > 16) { + r1 = Rand64(); + r2 = Rand64(); + r3 = Rand64(); + r4 = Rand64(); + r5 = Rand64(); + + pBuffer64[0] = r1; + pBuffer64[1] = r2; + pBuffer64[2] = r3; + pBuffer64[3] = r4; + pBuffer64[4] = r5; + + // + // Throw in some rotates so that the below numbers + // aren't the xor sum of previous numbers. + // + + r1 = _rotl64(r1, 7); + pBuffer64[5] = r1 ^ r2; + pBuffer64[6] = r1 ^ r3; + pBuffer64[7] = r1 ^ r4; + pBuffer64[8] = r1 ^ r5; + + r2 = _rotl64(r2, 13); + pBuffer64[9] = r2 ^ r3; + pBuffer64[10] = r2 ^ r4; + pBuffer64[11] = r2 ^ r5; + + r3 = _rotl64(r3, 19); + pBuffer64[12] = r3 ^ r4; + pBuffer64[13] = r3 ^ r5; + + pBuffer64[14] = r1 ^ r2 ^ r3; + pBuffer64[15] = r1 ^ _rotl64(r4 ^ r5, 39); + + pBuffer64 += 16; + Remaining -= 16; + } + } + + // + // Fill in the tail of the buffer + // + + while (Remaining >= 4) { + r1 = Rand64(); + r2 = Rand64(); + r3 = Rand64(); + r4 = Rand64(); + + pBuffer64[0] = r1; + pBuffer64[1] = r2; + pBuffer64[2] = r3; + pBuffer64[3] = r4; + + pBuffer64 += 4; + Remaining -= 4; + } + + while (Remaining != 0) { + *pBuffer64 = Rand64(); + pBuffer64++; + Remaining--; + } + + if (ulLength != 0) { + r1 = Rand64(); + + while (ulLength != 0) { + *pBuffer = (BYTE)(r1 & 0xFF); + r1 >>= 8; + pBuffer++; + ulLength--; + } + } +} + +string Util::DoubleToStringHelper(const double d) +{ + char szFloatBuffer[100]; + sprintf_s(szFloatBuffer, _countof(szFloatBuffer), "%10.3lf", d); + + return string(szFloatBuffer); +} + +string ThreadTarget::GetXml(UINT32 indent) const +{ + char buffer[4096]; + string sXml; + + AddXmlInc(sXml, "\n"); + + sprintf_s(buffer, _countof(buffer), "%u\n", _ulThread); + AddXml(sXml, buffer); + + if (_ulWeight != 0) + { + sprintf_s(buffer, _countof(buffer), "%u\n", _ulWeight); + AddXml(sXml, buffer); + } + + AddXmlDec(sXml, "\n"); + + return sXml; +} + +string Target::GetXml(UINT32 indent) const +{ + char buffer[4096]; + string sXml; + + AddXmlInc(sXml, "\n"); + AddXml(sXml, "" + _sPath + "\n"); + + sprintf_s(buffer, _countof(buffer), "%u\n", _dwBlockSize); + AddXml(sXml, buffer); + + sprintf_s(buffer, _countof(buffer), "%I64u\n", _ullBaseFileOffset); + AddXml(sXml, buffer); + + AddXml(sXml, _fSequentialScanHint ? "true\n" : "false\n"); + AddXml(sXml, _fRandomAccessHint ? "true\n" : "false\n"); + AddXml(sXml, _fTemporaryFileHint ? "true\n" : "false\n"); + AddXml(sXml, _fUseLargePages ? "true\n" : "false\n"); + + // TargetCacheMode::Cached is implied default + switch (_cacheMode) + { + case TargetCacheMode::DisableLocalCache: + AddXml(sXml, "true\n"); + break; + case TargetCacheMode::DisableOSCache: + AddXml(sXml, "true\n"); + break; + } + + // WriteThroughMode::Off is implied default + switch (_writeThroughMode) + { + case WriteThroughMode::On: + AddXml(sXml, "true\n"); + break; + } + + // MemoryMappedIoMode::Off is implied default + switch (_memoryMappedIoMode) + { + case MemoryMappedIoMode::On: + AddXml(sXml, "true\n"); + break; + } + + // MemoryMappedIoFlushMode::Undefined is implied default + switch (_memoryMappedIoFlushMode) + { + case MemoryMappedIoFlushMode::ViewOfFile: + AddXml(sXml, "ViewOfFile\n"); + break; + case MemoryMappedIoFlushMode::NonVolatileMemory: + AddXml(sXml, "NonVolatileMemory\n") + break; + case MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain: + AddXml(sXml, "NonVolatileMemoryNoDrain\n"); + break; + } + + AddXmlInc(sXml, "\n"); + if (_fZeroWriteBuffers) + { + AddXml(sXml, "zero\n"); + } + else if (_cbRandomDataWriteBuffer == 0) + { + AddXml(sXml, "sequential\n"); + } + else + { + AddXml(sXml, "random\n"); + AddXmlInc(sXml, "\n"); + sprintf_s(buffer, _countof(buffer), "%I64u\n", _cbRandomDataWriteBuffer); + AddXml(sXml, buffer); + if (_sRandomDataWriteBufferSourcePath != "") + { + AddXml(sXml, "" + _sRandomDataWriteBufferSourcePath + "\n"); + } + AddXmlDec(sXml, "\n"); + } + AddXmlDec(sXml, "\n"); + + AddXml(sXml, _fParallelAsyncIO ? "true\n" : "false\n"); + + if (_fUseBurstSize) + { + sprintf_s(buffer, _countof(buffer), "%u\n", _dwBurstSize); + AddXml(sXml, buffer); + } + + if (_fThinkTime) + { + sprintf_s(buffer, _countof(buffer), "%u\n", _dwThinkTime); + AddXml(sXml, buffer); + } + + if (_fCreateFile) + { + sprintf_s(buffer, _countof(buffer), "%I64u\n", _ullFileSize); + AddXml(sXml, buffer); + } + + // If XML contains , is ignored + if (_ulRandomRatio > 0) + { + sprintf_s(buffer, _countof(buffer), "%I64u\n", GetBlockAlignmentInBytes()); + AddXml(sXml, buffer); + + // 100% random is alone + if (_ulRandomRatio != 100) + { + sprintf_s(buffer, _countof(buffer), "%u\n", GetRandomRatio()); + AddXml(sXml, buffer); + } + + // Distributions only occur in profiles with random IO. + + if (_vDistributionRange.size()) + { + char *type = nullptr; + + switch (_distributionType) + { + case DistributionType::Absolute: + type = "Absolute"; + break; + + case DistributionType::Percent: + type = "Percent"; + break; + + default: + assert(false); + } + + AddXmlInc(sXml, "\n"); + AddXmlInc(sXml, "<"); + sXml += type; + sXml += ">\n"; + + for (auto r : _vDistributionRange) + { + sprintf_s(buffer, _countof(buffer), "%I64u", r._span, r._dst.second); + AddXml(sXml, buffer); + sXml += "\n"; + } + + AddXmlDec(sXml, "\n"; + AddXmlDec(sXml, "\n"); + } + } + else + { + sprintf_s(buffer, _countof(buffer), "%I64u\n", GetBlockAlignmentInBytes()); + AddXml(sXml, buffer); + + AddXml(sXml, _fInterlockedSequential ? + "true\n" : + "false\n"); + } + + sprintf_s(buffer, _countof(buffer), "%I64u\n", _ullThreadStride); + AddXml(sXml, buffer); + + sprintf_s(buffer, _countof(buffer), "%I64u\n", _ullMaxFileSize); + AddXml(sXml, buffer); + + sprintf_s(buffer, _countof(buffer), "%u\n", _dwRequestCount); + AddXml(sXml, buffer); + + sprintf_s(buffer, _countof(buffer), "%u\n", _ulWriteRatio); + AddXml(sXml, buffer); + + // Preserve specified units + if (_dwThroughputIOPS) + { + sprintf_s(buffer, _countof(buffer), "%u\n", _dwThroughputIOPS); + AddXml(sXml, buffer); + } + else + { + sprintf_s(buffer, _countof(buffer), "%u\n", _dwThroughputBytesPerMillisecond); + AddXml(sXml, buffer); + } + + sprintf_s(buffer, _countof(buffer), "%u\n", _dwThreadsPerFile); + AddXml(sXml, buffer); + + if (_ioPriorityHint == IoPriorityHintVeryLow) + { + AddXml(sXml, "1\n"); + } + else if (_ioPriorityHint == IoPriorityHintLow) + { + AddXml(sXml, "2\n"); + } + else if (_ioPriorityHint == IoPriorityHintNormal) + { + AddXml(sXml, "3\n"); + } + else + { + AddXml(sXml, "* UNSUPPORTED *\n"); + } + + sprintf_s(buffer, _countof(buffer), "%u\n", _ulWeight); + AddXml(sXml, buffer); + + if (_vThreadTargets.size() > 0) + { + AddXmlInc(sXml, "\n"); + + for (const auto& threadTarget : _vThreadTargets) + { + sXml += threadTarget.GetXml(indent); + } + + AddXmlDec(sXml, "\n"); + } + + AddXmlDec(sXml, "\n"); + + return sXml; +} + +bool Target::_FillRandomDataWriteBuffer(Random *pRand) +{ + assert(_pRandomDataWriteBuffer != nullptr); + bool fOk = true; + size_t cb = static_cast(GetRandomDataWriteBufferSize()); + if (GetRandomDataWriteBufferSourcePath() == "") + { + pRand->RandBuffer(_pRandomDataWriteBuffer, (UINT32)cb, false); + } + else + { + // fill buffer from file + HANDLE hFile = CreateFile(GetRandomDataWriteBufferSourcePath().c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr); + if (hFile != INVALID_HANDLE_VALUE) + { + UINT64 cbLeftToRead = GetRandomDataWriteBufferSize(); + BYTE *pBuffer = _pRandomDataWriteBuffer; + bool fReadSuccess = true; + while (fReadSuccess && cbLeftToRead > 0) + { + DWORD cbToRead = static_cast(min(64 * 1024, cbLeftToRead)); + DWORD cbRead; + fReadSuccess = ((ReadFile(hFile, pBuffer, cbToRead, &cbRead, nullptr) == TRUE) && (cbRead > 0)); + pBuffer += cbRead; + } + + // if the file is smaller than the buffer, repeat its content + BYTE *pSource = _pRandomDataWriteBuffer; + const BYTE *pPastEnd = pSource + GetRandomDataWriteBufferSize(); + while (pBuffer < pPastEnd) + { + *pBuffer++ = *pSource++; + } + CloseHandle(hFile); + } + else + { + printf("\n\nERROR: Unable to open entropy file '%s'\n\n", GetRandomDataWriteBufferSourcePath().c_str()); + fOk = false; + } + } + return fOk; +} + +bool Target::AllocateAndFillRandomDataWriteBuffer(Random *pRand) +{ + assert(_pRandomDataWriteBuffer == nullptr); + bool fOk = false; + size_t cb = static_cast(GetRandomDataWriteBufferSize()); + if (cb < 1) + { + return fOk; + } + + // TODO: make sure the size if <= max value for size_t + if (GetUseLargePages()) + { + size_t cbMinLargePage = GetLargePageMinimum(); + size_t cbRoundedSize = (cb + cbMinLargePage - 1) & ~(cbMinLargePage - 1); + _pRandomDataWriteBuffer = (BYTE *)VirtualAlloc(nullptr, cbRoundedSize, MEM_COMMIT | MEM_RESERVE | MEM_LARGE_PAGES, PAGE_EXECUTE_READWRITE); + } + else + { + _pRandomDataWriteBuffer = (BYTE *)VirtualAlloc(nullptr, cb, MEM_COMMIT, PAGE_READWRITE); + } + + fOk = (_pRandomDataWriteBuffer != nullptr); + if (fOk) + { + fOk = _FillRandomDataWriteBuffer(pRand); + } + return fOk; +} + +void Target::FreeRandomDataWriteBuffer() +{ + if (nullptr != _pRandomDataWriteBuffer) + { + VirtualFree(_pRandomDataWriteBuffer, 0, MEM_RELEASE); + _pRandomDataWriteBuffer = nullptr; + } +} + +BYTE* Target::GetRandomDataWriteBuffer(Random *pRand) +{ + size_t cbBuffer = static_cast(GetRandomDataWriteBufferSize()); + size_t cbBlock = GetBlockSizeInBytes(); + + // leave enough bytes in the buffer for one block + size_t randomOffset = pRand->Rand32() % (cbBuffer - (cbBlock - 1)); + + bool fUnbufferedIO = (_cacheMode == TargetCacheMode::DisableOSCache); + if (fUnbufferedIO) + { + // for unbuffered IO, offset in the buffer needs to be 512-byte aligned + const size_t cbAlignment = 512; + randomOffset -= (randomOffset % cbAlignment); + } + + BYTE *pBuffer = reinterpret_cast(reinterpret_cast(_pRandomDataWriteBuffer)+randomOffset); + + // unbuffered IO needs aligned addresses + assert(!fUnbufferedIO || (reinterpret_cast(pBuffer) % 512 == 0)); + assert(pBuffer >= _pRandomDataWriteBuffer); + assert(pBuffer <= _pRandomDataWriteBuffer + GetRandomDataWriteBufferSize() - GetBlockSizeInBytes()); + + return pBuffer; +} + +string TimeSpan::GetXml(UINT32 indent) const +{ + string sXml; + char buffer[4096]; + + AddXmlInc(sXml, "\n"); + AddXml(sXml, _fCompletionRoutines ? "true\n" : "false\n"); + AddXml(sXml,_fMeasureLatency ? "true\n" : "false\n"); + AddXml(sXml, _fCalculateIopsStdDev ? "true\n" : "false\n"); + AddXml(sXml, _fDisableAffinity ? "true\n" : "false\n"); + + sprintf_s(buffer, _countof(buffer), "%u\n", _ulDuration); + AddXml(sXml, buffer); + + sprintf_s(buffer, _countof(buffer), "%u\n", _ulWarmUp); + AddXml(sXml, buffer); + + sprintf_s(buffer, _countof(buffer), "%u\n", _ulCoolDown); + AddXml(sXml, buffer); + + sprintf_s(buffer, _countof(buffer), "%u\n", _dwThreadCount); + AddXml(sXml, buffer); + + sprintf_s(buffer, _countof(buffer), "%u\n", _dwRequestCount); + AddXml(sXml, buffer); + + sprintf_s(buffer, _countof(buffer), "%u\n", _ulIoBucketDurationInMilliseconds); + AddXml(sXml, buffer); + + sprintf_s(buffer, _countof(buffer), "%u\n", _ulRandSeed); + AddXml(sXml, buffer); + + if (_vAffinity.size() > 0) + { + AddXmlInc(sXml, "\n"); + for (const auto& a : _vAffinity) + { + sprintf_s(buffer, _countof(buffer), "\n", a.wGroup, a.bProc); + AddXml(sXml, buffer); + } + AddXmlDec(sXml, "\n"); + } + + AddXmlInc(sXml, "\n"); + for (const auto& target : _vTargets) + { + sXml += target.GetXml(indent); + } + AddXmlDec(sXml, "\n"); + AddXmlDec(sXml, "\n"); + return sXml; +} + +void TimeSpan::MarkFilesAsPrecreated(const vector vFiles) +{ + for (auto sFile : vFiles) + { + for (auto pTarget = _vTargets.begin(); pTarget != _vTargets.end(); pTarget++) + { + if (sFile == pTarget->GetPath()) + { + pTarget->SetPrecreated(true); + } + } + } +} + +string Profile::GetXml(UINT32 indent) const +{ + string sXml; + char buffer[4096]; + + AddXmlInc(sXml, "\n"); + + sprintf_s(buffer, _countof(buffer), "%u\n", _dwProgress); + AddXml(sXml, buffer); + + if (g_ExperimentFlags) + { + // only output if on so that downlevel doesn't get (and fail: not in downlevel xsd) unless actually specified + sprintf_s(buffer, _countof(buffer), "%u\n", g_ExperimentFlags); + AddXml(sXml, buffer); + } + + if (_resultsFormat == ResultsFormat::Text) + { + AddXml(sXml, "text\n"); + } + else if (_resultsFormat == ResultsFormat::Xml) + { + AddXml(sXml, "xml\n"); + } + else + { + AddXml(sXml, "* UNSUPPORTED *\n"); + } + + AddXml(sXml, _fVerbose ? "true\n" : "false\n"); + if (_fVerboseStats) + { + // only output if on so that downlevel doesn't get (and fail: not in downlevel xsd) unless actually specified + AddXml(sXml, "true\n"); + } + + if (_precreateFiles == PrecreateFiles::UseMaxSize) + { + AddXml(sXml, "UseMaxSize\n"); + } + else if (_precreateFiles == PrecreateFiles::OnlyFilesWithConstantSizes) + { + AddXml(sXml, "CreateOnlyFilesWithConstantSizes\n"); + } + else if (_precreateFiles == PrecreateFiles::OnlyFilesWithConstantOrZeroSizes) + { + AddXml(sXml, "CreateOnlyFilesWithConstantOrZeroSizes\n"); + } + + if (_fEtwEnabled) + { + AddXmlInc(sXml, "\n"); + AddXml(sXml, _fEtwProcess ? "true\n" : "false\n"); + AddXml(sXml, _fEtwThread ? "true\n" : "false\n"); + AddXml(sXml, _fEtwImageLoad ? "true\n" : "false\n"); + AddXml(sXml, _fEtwDiskIO ? "true\n" : "false\n"); + AddXml(sXml, _fEtwMemoryPageFaults ? "true\n" : "false\n"); + AddXml(sXml, _fEtwMemoryHardFaults ? "true\n" : "false\n"); + AddXml(sXml, _fEtwNetwork ? "true\n" : "false\n"); + AddXml(sXml, _fEtwRegistry ? "true\n" : "false\n"); + AddXml(sXml, _fEtwUsePagedMemory ? "true\n" : "false\n"); + AddXml(sXml, _fEtwUsePerfTimer ? "true\n" : "false\n"); + AddXml(sXml, _fEtwUseSystemTimer ? "true\n" : "false\n"); + AddXml(sXml, _fEtwUseCyclesCounter ? "true\n" : "false\n"); + AddXmlDec(sXml, "\n"); + } + + AddXmlInc(sXml, "\n"); + for (const auto& timespan : _vTimeSpans) + { + sXml += timespan.GetXml(indent); + } + AddXmlDec(sXml, "\n"); + AddXmlDec(sXml, "\n"); + return sXml; +} + +void Profile::MarkFilesAsPrecreated(const vector vFiles) +{ + for (auto pTimeSpan = _vTimeSpans.begin(); pTimeSpan != _vTimeSpans.end(); pTimeSpan++) + { + pTimeSpan->MarkFilesAsPrecreated(vFiles); + } +} + +bool Profile::Validate(bool fSingleSpec, SystemInformation *pSystem) const +{ + bool fOk = true; + + if (GetTimeSpans().size() == 0) + { + fprintf(stderr, "ERROR: no timespans specified\n"); + fOk = false; + } + else + { + for (const auto& timeSpan : GetTimeSpans()) + { + if (pSystem != nullptr) + { + for (const auto& Affinity : timeSpan.GetAffinityAssignments()) + { + if (Affinity.wGroup >= pSystem->processorTopology._vProcessorGroupInformation.size()) + { + fprintf(stderr, "ERROR: affinity assignment to group %u; system only has %u groups\n", + Affinity.wGroup, + (int) pSystem->processorTopology._vProcessorGroupInformation.size()); + + fOk = false; + + } + if (fOk && !pSystem->processorTopology._vProcessorGroupInformation[Affinity.wGroup].IsProcessorValid(Affinity.bProc)) + { + fprintf(stderr, "ERROR: affinity assignment to group %u cpu %u not possible; group has a max of %u cpus\n", + Affinity.wGroup, + Affinity.bProc, + pSystem->processorTopology._vProcessorGroupInformation[Affinity.wGroup]._maximumProcessorCount); + + fOk = false; + } + + if (fOk && !pSystem->processorTopology._vProcessorGroupInformation[Affinity.wGroup].IsProcessorActive(Affinity.bProc)) + { + fprintf(stderr, "ERROR: affinity assignment to group %u cpu %u not possible; cpu is not active (current mask 0x%p)\n", + Affinity.wGroup, + Affinity.bProc, + (void *) pSystem->processorTopology._vProcessorGroupInformation[Affinity.wGroup]._activeProcessorMask); + + fOk = false; + } + } + } + + // ISSUE: many of the following validation errors are stated in cmdline terms, which is not helpful for XML + + if (timeSpan.GetDisableAffinity() && timeSpan.GetAffinityAssignments().size() > 0) + { + fprintf(stderr, "ERROR: -n and -a parameters cannot be used together\n"); + fOk = false; + } + + // ISSUE: with XML and the following the target specification validation it would be useful to say what + // target they're for + + for (const auto& target : timeSpan.GetTargets()) + { + const bool targetHasMultipleThreads = (timeSpan.GetThreadCount() > 1) || (target.GetThreadsPerFile() > 1); + + if (timeSpan.GetThreadCount() > 0 && target.GetThreadsPerFile() > 1) + { + fprintf(stderr, "ERROR: -F and -t parameters cannot be used together\n"); + fOk = false; + } + + if (target.GetThroughputInBytesPerMillisecond() > 0 && timeSpan.GetCompletionRoutines()) + { + fprintf(stderr, "ERROR: -g throughput control cannot be used with -x completion routines\n"); + fOk = false; + } + + // If burst size is specified think time must be specified and If think time is specified burst size should be non zero + if ((target.GetThinkTime() == 0 && target.GetBurstSize() > 0) || (target.GetThinkTime() > 0 && target.GetBurstSize() == 0)) + { + fprintf(stderr, "ERROR: need to specify -j with -i\n"); + fOk = false; + } + + if (timeSpan.GetThreadCount() > 0 && timeSpan.GetRequestCount() > 0) + { + if (target.GetThroughputInBytesPerMillisecond() > 0) + { + fprintf(stderr, "ERROR: -g throughput control cannot be used with -O outstanding requests per thread\n"); + fOk = false; + } + + if (target.GetThinkTime() > 0) + { + fprintf(stderr, "ERROR: -j think time cannot be used with -O outstanding requests per thread\n"); + fOk = false; + } + + if (target.GetUseParallelAsyncIO()) + { + fprintf(stderr, "ERROR: -p parallel IO cannot be used with -O outstanding requests per thread\n"); + fOk = false; + } + + if (target.GetWeight() == 0) + { + fprintf(stderr, "ERROR: a non-zero target Weight must be specified\n"); + fOk = false; + } + + for (const auto& threadTarget : target.GetThreadTargets()) + { + if (threadTarget.GetThread() >= timeSpan.GetThreadCount()) + { + fprintf(stderr, "ERROR: illegal thread specified for ThreadTarget\n"); + fOk = false; + } + } + } + else if (target.GetThreadTargets().size() != 0) + { + fprintf(stderr, "ERROR: ThreadTargets can only be specified when the timespan ThreadCount and RequestCount are specified\n"); + fOk = false; + } + + if (target.GetRandomRatio()) + { + if (target.GetThreadStrideInBytes() > 0) + { + fprintf(stderr, "ERROR: -T conflicts with -r\n"); + fOk = false; + // although ullThreadStride==0 is a valid value, it's interpreted as "not provided" for this warning + } + + if (target.GetUseInterlockedSequential()) + { + fprintf(stderr, "ERROR: -si conflicts with -r\n"); + fOk = false; + } + + if (target.GetUseParallelAsyncIO()) + { + fprintf(stderr, "ERROR: -p conflicts with -r\n"); + fOk = false; + } + } + else + { + if (target.GetDistributionRange().size() != 0) + { + fprintf(stderr, "ERROR: random distribution ranges (-rd) do not apply to sequential-only IO patterns\n"); + fOk = false; + } + + if (target.GetUseParallelAsyncIO() && target.GetRequestCount() == 1) + { + fprintf(stderr, "WARNING: -p does not have effect unless outstanding I/O count (-o) is > 1\n"); + } + + if (target.GetUseInterlockedSequential()) + { + if (target.GetThreadStrideInBytes() > 0) + { + fprintf(stderr, "ERROR: -si conflicts with -T\n"); + fOk = false; + } + + if (target.GetUseParallelAsyncIO()) + { + fprintf(stderr, "ERROR: -si conflicts with -p\n"); + fOk = false; + } + + if (!targetHasMultipleThreads) + { + fprintf(stderr, "WARNING: single-threaded test, -si ignored\n"); + } + } + else + { + if (targetHasMultipleThreads && !target.GetThreadStrideInBytes()) + { + fprintf(stderr, "WARNING: target access pattern will not be sequential, consider -si\n"); + } + + if (!targetHasMultipleThreads && target.GetThreadStrideInBytes()) + { + fprintf(stderr, "ERROR: -T has no effect unless multiple threads per target are used\n"); + fOk = false; + } + } + } + + // Distribution ranges are only applied to random loads. Note validation failure in the sequential case. + // TBD this should be moved to a proper Distribution class. + { + UINT32 ioAcc = 0; + UINT64 targetAcc = 0; + bool absZero = false, absZeroLast = false; + for (const auto& r : target.GetDistributionRange()) + { + if (target.GetDistributionType() == DistributionType::Absolute) + { + // allow zero target span in last position + absZeroLast = false; + if (r._dst.second == 0 && !absZero) + { + // legal in last position + absZero = absZeroLast = true; + } + else if (r._dst.second < target.GetBlockSizeInBytes()) + { + fprintf(stderr, "ERROR: invalid random distribution target range %I64u - must be a minimum of the specified block size (%u bytes)\n", r._dst.second, target.GetBlockSizeInBytes()); + fOk = false; + break; + } + } + + // Validate accumulating IO% + if (ioAcc + r._span > 100) + { + fprintf(stderr, "ERROR: invalid random distribution IO%% %u: can be at most %u - total must be <= 100%%\n", r._span, 100 - ioAcc); + fOk = false; + break; + } + + // Validate accumulating Target% + // Note that absolute range needs no additional validation - known nonzero/large enough for IO + if (target.GetDistributionType() == DistributionType::Percent) + { + if (targetAcc + r._dst.second > 100) + { + fprintf(stderr, "ERROR: invalid random distribution Target%% %I64u: can be at most %I64u - total must be <= 100%%\n", r._dst.second, 100 - targetAcc); + fOk = false; + break; + } + + // Consuming the target before consuming the IO is invalid. + // No holes in IO. + if (targetAcc + r._dst.second == 100 && ioAcc + r._span < 100) + { + fprintf(stderr, "ERROR: invalid random distribution: the target is covered with %u%% IO left to distribute\n", 100 - (ioAcc + r._span)); + fOk = false; + break; + } + } + + ioAcc += r._span; + targetAcc += r._dst.second; + } + + // Percent dist Target% must sum to 100%. IO% underflow (either due to early Target% 100% or Target% overflow) is handled above. + if (target.GetDistributionType() == DistributionType::Percent && + targetAcc != 100) + { + fprintf(stderr, "ERROR: invalid random distribution span: Target%% (%I64u%%) must total 100%%\n", targetAcc); + fOk = false; + } + if (absZero && !absZeroLast) + { + fprintf(stderr, "ERROR: invalid zero target range in random distribution - must be a minimum of the specified block size (%u bytes)\n", target.GetBlockSizeInBytes()); + fOk = false; + } + } + + if (target.GetRandomDataWriteBufferSize() > 0) + { + if (target.GetRandomDataWriteBufferSize() < target.GetBlockSizeInBytes()) + { + fprintf(stderr, "ERROR: custom write buffer (-Z) is smaller than the block size. Write buffer size: %I64u block size: %u\n", + target.GetRandomDataWriteBufferSize(), + target.GetBlockSizeInBytes()); + fOk = false; + } + } + + if (target.GetMemoryMappedIoMode() == MemoryMappedIoMode::On) + { + if (timeSpan.GetCompletionRoutines()) + { + fprintf(stderr, "ERROR: completion routines (-x) can't be used with memory mapped IO (-Sm)\n"); + fOk = false; + } + if (target.GetCacheMode() == TargetCacheMode::DisableOSCache) + { + fprintf(stderr, "ERROR: unbuffered IO (-Su or -Sh) can't be used with memory mapped IO (-Sm)\n"); + fOk = false; + } + } + + if (target.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off && + target.GetMemoryMappedIoFlushMode() != MemoryMappedIoFlushMode::Undefined) + { + fprintf(stderr, "ERROR: memory mapped flush mode (-N) can only be specified with memory mapped IO (-Sm)\n"); + fOk = false; + } + + if (GetProfileOnly() == false) + { + auto sPath = target.GetPath(); + + if (sPath[0] == TEMPLATE_TARGET_PREFIX) + { + fprintf(stderr, "ERROR: template target '%s' was not substituted - all template targets must be substituted to run a profile\n", sPath.c_str()); + fOk = false; + } + } + + // Note that this error is only possible with -f or XML. The -Bbase:length form is immune. + if (target.GetMaxFileSize() && target.GetMaxFileSize() <= target.GetBaseFileOffsetInBytes()) + { + fprintf(stderr, "ERROR: maximum (-f) target offset must be greater than base (-B)\n"); + fOk = false; + } + + // If we know there is only a single target specification (the parameters which apply to targets) shared + // across the one or more targets, we can stop. In practical terms this is the command line case - for + // XML we don't know, and do need to keep going. This early exit lets us avoid repeating the same sets + // of error messages per each target we would otherwise loop over. + // + // If we ever did target property validation (say, v. its size) we'd want to divide out the validations + // into parameter-only v. parameter/property cases for similar reasons. + if (fSingleSpec) + { + break; + } + } + } + } + + return fOk; +} + +bool ThreadParameters::AllocateAndFillBufferForTarget(const Target& target) +{ + bool fOk = true; + BYTE *pDataBuffer = nullptr; + DWORD requestCount = target.GetRequestCount(); + size_t cbDataBuffer; + + // Use global request count + if (pTimeSpan->GetThreadCount() != 0 && + pTimeSpan->GetRequestCount() != 0) { + + requestCount = pTimeSpan->GetRequestCount(); + } + + // Create separate read & write buffers so the write content doesn't get overriden by reads + cbDataBuffer = (size_t) target.GetBlockSizeInBytes() * requestCount * 2; + if (target.GetUseLargePages()) + { + size_t cbMinLargePage = GetLargePageMinimum(); + size_t cbRoundedSize = (cbDataBuffer + cbMinLargePage - 1) & ~(cbMinLargePage - 1); + pDataBuffer = (BYTE *)VirtualAlloc(nullptr, cbRoundedSize, MEM_COMMIT | MEM_RESERVE | MEM_LARGE_PAGES, PAGE_EXECUTE_READWRITE); + } + else + { + pDataBuffer = (BYTE *)VirtualAlloc(nullptr, cbDataBuffer, MEM_COMMIT, PAGE_READWRITE); + } + + fOk = (pDataBuffer != nullptr); + + //fill buffer (useful only for write tests) + if (fOk && target.GetWriteRatio() > 0) + { + if (target.GetZeroWriteBuffers()) + { + memset(pDataBuffer, 0, cbDataBuffer); + } + else + { + for (size_t i = 0; i < cbDataBuffer; i++) + { + pDataBuffer[i] = (BYTE)(i % 256); + } + } + } + + if (fOk) + { + vpDataBuffers.push_back(pDataBuffer); + vulReadBufferSize.push_back(cbDataBuffer / 2); + } + + return fOk; +} + +BYTE* ThreadParameters::GetReadBuffer(size_t iTarget, size_t iRequest) +{ + return vpDataBuffers[iTarget] + (iRequest * vTargets[iTarget].GetBlockSizeInBytes()); +} + +BYTE* ThreadParameters::GetWriteBuffer(size_t iTarget, size_t iRequest) +{ + BYTE *pBuffer = nullptr; + + Target& target(vTargets[iTarget]); + size_t cb = static_cast(target.GetRandomDataWriteBufferSize()); + if (cb == 0) + { + pBuffer = vpDataBuffers[iTarget] + vulReadBufferSize[iTarget] + (iRequest * vTargets[iTarget].GetBlockSizeInBytes()); + + // + // This is a very efficient algorithm for generating random content at + // run-time. When tested in a single-threaded, CPU limited environment + // with 4K random writes, doing memset to fill the buffer got 112K IOPS, + // this algorithm got 111K IOPS. Using a static buffer got 118K IOPS. + // This was tested with a 64-bit diskspd.exe. With a 32-bit version it + // may be more efficient to do 32-bit operations. + // + + if (pTimeSpan->GetRandomWriteData() && + !target.GetZeroWriteBuffers()) + { + pRand->RandBuffer(pBuffer, vTargets[iTarget].GetBlockSizeInBytes(), true); + } + } + else + { + pBuffer = target.GetRandomDataWriteBuffer(pRand); + } + return pBuffer; +} + +bool ThreadParameters::InitializeMappedViewForTarget(Target& target, DWORD DesiredAccess) +{ + bool fOk = true; + DWORD dwProtect = PAGE_READWRITE; + + if (DesiredAccess == GENERIC_READ) + { + dwProtect = PAGE_READONLY; + } + + HANDLE hFile = CreateFileMapping(target.GetMappedViewFileHandle(), NULL, dwProtect, 0, 0, NULL); + fOk = (hFile != NULL); + if (fOk) + { + DWORD dwDesiredAccess = FILE_MAP_WRITE; + + if (DesiredAccess == GENERIC_READ) + { + dwDesiredAccess = FILE_MAP_READ; + } + + BYTE *mapView = (BYTE*) MapViewOfFile(hFile, dwDesiredAccess, 0, 0, 0); + fOk = (mapView != NULL); + if (fOk) + { + target.SetMappedView(mapView); + } + else + { + fprintf(stderr, "FATAL ERROR: Could not map view for target '%s'. Error code: 0x%x\n", target.GetPath().c_str(), GetLastError()); + } + } + else + { + fprintf(stderr, "FATAL ERROR: Could not create a file mapping for target '%s'. Error code: 0x%x\n", target.GetPath().c_str(), GetLastError()); + } + return fOk; +} + +DWORD ThreadParameters::GetTotalRequestCount() const +{ + DWORD cRequests = 0; + + for (const auto& t : vTargets) + { + cRequests += t.GetRequestCount(); + } + + if (pTimeSpan->GetRequestCount() != 0 && + pTimeSpan->GetThreadCount() != 0) + { + cRequests = pTimeSpan->GetRequestCount(); + } + + return cRequests; +} + +void EtwResultParser::ParseResults(vector vResults) +{ + if (TraceLoggingProviderEnabled(g_hEtwProvider, + TRACE_LEVEL_NONE, + DISKSPD_TRACE_INFO)) + { + for (size_t ullResults = 0; ullResults < vResults.size(); ullResults++) + { + const Results& results = vResults[ullResults]; + for (size_t ullThread = 0; ullThread < results.vThreadResults.size(); ullThread++) + { + const ThreadResults& threadResults = results.vThreadResults[ullThread]; + for (const auto& targetResults : threadResults.vTargetResults) + { + if (targetResults.ullReadIOCount) + { + _WriteResults(IOOperation::ReadIO, targetResults, ullThread); + } + if (targetResults.ullWriteIOCount) + { + _WriteResults(IOOperation::WriteIO, targetResults, ullThread); + } + } + } + } + } +} + +void EtwResultParser::_WriteResults(IOOperation type, const TargetResults& targetResults, size_t ullThread) +{ + UINT64 ullIOCount = (type == IOOperation::ReadIO) ? targetResults.ullReadIOCount : targetResults.ullWriteIOCount; + UINT64 ullBytesCount = (type == IOOperation::ReadIO) ? targetResults.ullReadBytesCount : targetResults.ullWriteBytesCount; + + TraceLoggingWrite(g_hEtwProvider, + "Statistics", + TraceLoggingLevel((TRACE_LEVEL_NONE)), + TraceLoggingString((type == IOOperation::ReadIO) ? "Read" : "Write", "IO Type"), + TraceLoggingUInt64(ullThread, "Thread"), + TraceLoggingUInt64(ullBytesCount, "Bytes"), + TraceLoggingUInt64(ullIOCount, "IO Count"), + TraceLoggingString(targetResults.sPath.c_str(), "Path"), + TraceLoggingUInt64(targetResults.ullFileSize, "File Size")); +} diff --git a/CristalDiskMark/source/diskspd22/Common/Common.h b/CristalDiskMark/source/diskspd22/Common/Common.h new file mode 100644 index 0000000..ebd9487 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Common/Common.h @@ -0,0 +1,2695 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include //ntdll.dll +#include +#include "Histogram.h" +#include "IoBucketizer.h" +#include "ThroughputMeter.h" +#include "Version.h" + +using namespace std; + +TRACELOGGING_DECLARE_PROVIDER(g_hEtwProvider); + +#define DISKSPD_TRACE_INFO 0x00000000 +#define DISKSPD_TRACE_RESERVED 0x00000001 +#define DISKSPD_TRACE_IO 0x00000100 + +typedef void (WINAPI *PRINTF)(const char*, va_list); //function used for displaying formatted data (printf style) + +#define ROUND_DOWN(_x,_alignment) \ + ( ((_x)/(_alignment)) * (_alignment) ) + +#define ROUND_UP(_x,_alignment) \ + ROUND_DOWN((_x) + (_alignment) - 1, (_alignment)) + +#define TB (((UINT64)1)<<40) +#define GB (((UINT64)1)<<30) +#define MB (((UINT64)1)<<20) +#define KB (((UINT64)1)<<10) + +#define EXPERIMENT_TPUT_CALC 0x1 // precise ms sleep calculation for low rate throughput control +extern ULONG g_ExperimentFlags; + +struct ETWEventCounters +{ + UINT64 ullIORead; // Read + UINT64 ullIOWrite; // Write + UINT64 ullMMTransitionFault; // Transition fault + UINT64 ullMMDemandZeroFault; // Demand Zero fault + UINT64 ullMMCopyOnWrite; // Copy on Write + UINT64 ullMMGuardPageFault; // Guard Page fault + UINT64 ullMMHardPageFault; // Hard page fault + UINT64 ullNetTcpSend; // Send + UINT64 ullNetTcpReceive; // Receive + UINT64 ullNetUdpSend; // Send + UINT64 ullNetUdpReceive; // Receive + UINT64 ullNetConnect; // Connect + UINT64 ullNetDisconnect; // Disconnect + UINT64 ullNetRetransmit; // ReTransmit + UINT64 ullNetAccept; // Accept + UINT64 ullNetReconnect; // ReConnect + UINT64 ullRegCreate; // NtCreateKey + UINT64 ullRegOpen; // NtOpenKey + UINT64 ullRegDelete; // NtDeleteKey + UINT64 ullRegQuery; // NtQueryKey + UINT64 ullRegSetValue; // NtSetValueKey + UINT64 ullRegDeleteValue; // NtDeleteValueKey + UINT64 ullRegQueryValue; // NtQueryValueKey + UINT64 ullRegEnumerateKey; // NtEnumerateKey + UINT64 ullRegEnumerateValueKey; // NtEnumerateValueKey + UINT64 ullRegQueryMultipleValue; // NtQueryMultipleValueKey + UINT64 ullRegSetInformation; // NtSetInformationKey + UINT64 ullRegFlush; // NtFlushKey + UINT64 ullThreadStart; + UINT64 ullThreadEnd; + UINT64 ullProcessStart; + UINT64 ullProcessEnd; + UINT64 ullImageLoad; +}; + +// structure containing informations about ETW session +struct ETWSessionInfo +{ + ULONG ulBufferSize; + ULONG ulMinimumBuffers; + ULONG ulMaximumBuffers; + ULONG ulFreeBuffers; + ULONG ulBuffersWritten; + ULONG ulFlushTimer; + LONG lAgeLimit; + ULONG ulNumberOfBuffers; + ULONG ulEventsLost; + ULONG ulLogBuffersLost; + ULONG ulRealTimeBuffersLost; +}; + +// structure containing parameters concerning ETW session provided by user +struct ETWMask +{ + BOOL bProcess; + BOOL bThread; + BOOL bImageLoad; + BOOL bDiskIO; + BOOL bMemoryPageFaults; + BOOL bMemoryHardFaults; + BOOL bNetwork; + BOOL bRegistry; + BOOL bUsePagedMemory; + BOOL bUsePerfTimer; + BOOL bUseSystemTimer; + BOOL bUseCyclesCounter; +}; + +namespace UnitTests +{ + class PerfTimerUnitTests; + class ProfileUnitTests; + class TargetUnitTests; + class IORequestGeneratorUnitTests; +} + +class PerfTimer +{ +public: + + static UINT64 GetTime(); + + static double PerfTimeToMicroseconds(const double); + static double PerfTimeToMilliseconds(const double); + static double PerfTimeToSeconds(const double); + static double PerfTimeToMicroseconds(const UINT64); + static double PerfTimeToMilliseconds(const UINT64); + static double PerfTimeToSeconds(const UINT64); + + static UINT64 MicrosecondsToPerfTime(const double); + static UINT64 MillisecondsToPerfTime(const double); + static UINT64 SecondsToPerfTime(const double); + +private: + + static const UINT64 TIMER_FREQ; + static UINT64 _GetPerfTimerFreq(); + + friend class UnitTests::PerfTimerUnitTests; +}; + +template +class Range +{ +public: + Range( + T1 Source, + T1 Span, + T2 Dest + ) : + _src(Source), + _span(Span), + _dst(Dest) + {} + + constexpr bool operator<(const Range& other) const + { + // + // This is used for comparison of effective distributions during result reporting (dedup). + // + // A hole with _span == 0 sorts < range with _span > 0 + // Note that a hole will never match in a find(). + // + + return _src < other._src || + (_src == other._src && + (_span < other._span || + (_span == other._span && _dst < other._dst))); + } + + static Range const * find(const vector>& v, T1 c) + { + // v must be sorted + size_t s = 0, mid, e = v.size() - 1; + + while (true) + { + mid = s + ((e - s) / 2); + if (c < v[mid]._src) { + if (s == mid) + { + return nullptr; + } + e = mid - 1; + } + else if (c > v[mid]._src + v[mid]._span - 1) + { + if (e == mid) + { + return nullptr; + } + s = mid + 1; + } + else + { + return &v[mid]; + } + } + } + + T1 _src, _span; + T2 _dst; +}; + +typedef Range> DistributionRange; + +enum class DistributionType +{ + None, + Absolute, + Percent +}; + +// +// This code implements Bob Jenkins public domain simple random number generator +// See http://burtleburtle.net/bob/rand/smallprng.html for details +// + +class Random +{ +public: + Random(UINT64 ulSeed = 0); + + inline UINT64 Rand64() + { + UINT64 e; + + e = _ulState[0] - _rotl64(_ulState[1], 7); + _ulState[0] = _ulState[1] ^ _rotl64(_ulState[2], 13); + _ulState[1] = _ulState[2] + _rotl64(_ulState[3], 37); + _ulState[2] = _ulState[3] + e; + _ulState[3] = e + _ulState[0]; + + return _ulState[3]; + } + + inline UINT32 Rand32() + { + return (UINT32)Rand64(); + } + + void RandBuffer(BYTE *pBuffer, UINT32 ulLength, bool fPseudoRandomOkay); + +private: + UINT64 _ulState[4]; +}; + +struct PercentileDescriptor +{ + double Percentile; + string Name; +}; + +class Util +{ +public: + static string DoubleToStringHelper(const double); + template static T QuotientCeiling(T dividend, T divisor) + { + return (dividend + divisor - 1) / divisor; + } + + // True if result is <= ratio. + // The ratio is on the interval [0, 100]: + // 0 will never occur (always false) + // 100 will always occur (always true) + + static bool BooleanRatio(Random *pRand, UINT32 ulRatio) + { + return ((pRand->Rand32() % 100 + 1) <= ulRatio); + } + + // + // This is close to strtoul[l], returning the next character to parse in the input string. + // This character can be used for validation (should there be any non-integer remaining), + // interpreting units that follow the integer (KMGTB), or parsing further (int[]) + // content in the string. + // + // Return value indicates whether any integers were parsed to Output. Continue is only modified + // on success, and will point to the terminator on completion. False is returned on overflow. + // + + template + static bool ParseUInt(const char* Input, T& Output, const char*& Continue) + { + T current = 0, last = 0; + const char* input = Input; + bool parsed = false; + + while (*input) + { + if (*input < '0' || *input > '9') + { + break; + } + + parsed = true; + current *= 10; + current += static_cast(*input) - static_cast('0'); + + // + // Overflow? + // + + if (current < last) + { + parsed = false; + break; + } + last = current; + + input += 1; + } + + // + // Return if string was consumed + // + // + + if (parsed) + { + Continue = input; + Output = current; + } + + return parsed; + } +}; + +// To keep track of which type of IO was issued +enum class IOOperation +{ + Unknown = 0, + ReadIO, + WriteIO +}; + +class TargetResults +{ +public: + TargetResults() : + ullFileSize(0), + ullBytesCount(0), + ullIOCount(0), + ullReadBytesCount(0), + ullReadIOCount(0), + ullWriteBytesCount(0), + ullWriteIOCount(0) + { + + } + + void Add( + DWORD dwBytesTransferred, + IOOperation type, + UINT64 ullIoStartTime, + UINT64 ullIoEndTime, + UINT64 ullSpanStartTime, + bool fMeasureLatency, + bool fCalculateIopsStdDev + ) + { + if (type == IOOperation::ReadIO) + { + ullReadBytesCount += dwBytesTransferred; // update read bytes counter + ullReadIOCount++; // update completed read I/O operations counter + } + else + { + ullWriteBytesCount += dwBytesTransferred; // update write bytes counter + ullWriteIOCount++; // update completed write I/O operations counter + } + + ullBytesCount += dwBytesTransferred; // update bytes counter + ullIOCount++; // update completed I/O operations counter + + // end time is 0 if we're not measuring latency + assert(((fMeasureLatency || fCalculateIopsStdDev) && ullIoEndTime != 0) || + (!fMeasureLatency && !fCalculateIopsStdDev)); + + if (ullIoEndTime == 0) + { + return; + } + + UINT64 ullDuration = ullIoEndTime - ullIoStartTime;; + double lfDurationUsec = PerfTimer::PerfTimeToMicroseconds(ullDuration); + + if (fMeasureLatency) + { + if (type == IOOperation::ReadIO) + { + readLatencyHistogram.Add(static_cast(lfDurationUsec)); + } + else + { + writeLatencyHistogram.Add(static_cast(lfDurationUsec)); + } + } + + if (fCalculateIopsStdDev) + { + UINT64 ullRelativeCompletionTime = ullIoEndTime - ullSpanStartTime; + + if (type == IOOperation::ReadIO) + { + readBucketizer.Add(ullRelativeCompletionTime, lfDurationUsec); + } + else + { + writeBucketizer.Add(ullRelativeCompletionTime, lfDurationUsec); + } + } + } + + string sPath; + UINT64 ullFileSize; //size of the file + UINT64 ullBytesCount; //number of accessed bytes + UINT64 ullIOCount; //number of performed I/O operations + UINT64 ullReadBytesCount; //number of bytes read + UINT64 ullReadIOCount; //number of performed Read I/O operations + UINT64 ullWriteBytesCount; //number of bytes written + UINT64 ullWriteIOCount; //number of performed Write I/O operations + + Histogram readLatencyHistogram; + Histogram writeLatencyHistogram; + + IoBucketizer readBucketizer; + IoBucketizer writeBucketizer; + + // Effective distribution after applying to target size (if specified/non-empty) + vector vDistributionRange; +}; + +typedef struct _WAIT_STATS { + ULONGLONG Wait; + ULONGLONG ThrottleWait; + ULONGLONG ThrottleSleep; + ULONGLONG Lookaside; + ULONGLONG LookasideCompletion[8]; // 0 == none, 1 == 1, ... 7 = 7+ +} WAIT_STATS; + +class ThreadResults +{ +public: + ThreadResults() + { + WaitStats = { 0 }; + } + + WAIT_STATS WaitStats; + vector vTargetResults; +}; + +class Results +{ +public: + bool fUseETW; + struct ETWEventCounters EtwEventCounters; + struct ETWMask EtwMask; + struct ETWSessionInfo EtwSessionInfo; + vector vThreadResults; + UINT64 ullTimeCount; + vector vSystemProcessorPerfInfo; +}; + +typedef void (*CALLBACK_TEST_STARTED)(); //callback function to notify that the measured test is about to start +typedef void (*CALLBACK_TEST_FINISHED)(); //callback function to notify that the measured test has just finished + +class ProcessorGroupInformation +{ +public: + WORD _groupNumber; + BYTE _maximumProcessorCount; + BYTE _activeProcessorCount; + KAFFINITY _activeProcessorMask; + + ProcessorGroupInformation() = delete; + ProcessorGroupInformation( + WORD Group, + BYTE MaximumProcessorCount, + BYTE ActiveProcessorCount, + KAFFINITY ActiveProcessorMask) : + _groupNumber(Group), + _maximumProcessorCount(MaximumProcessorCount), + _activeProcessorCount(ActiveProcessorCount), + _activeProcessorMask(ActiveProcessorMask) + { + } + + ProcessorGroupInformation( + WORD Group, + PROCESSOR_GROUP_INFO& GroupInfo) : + _groupNumber(Group), + _maximumProcessorCount(GroupInfo.MaximumProcessorCount), + _activeProcessorCount(GroupInfo.ActiveProcessorCount), + _activeProcessorMask(GroupInfo.ActiveProcessorMask) + { + } + + // This logic is strictly unaware that sparse processor masks are not possible; + // address this later, not important. See comments around RelationGroup query. + bool IsProcessorActive(BYTE Processor) const + { + return (IsProcessorValid(Processor) && + (((KAFFINITY)1 << Processor) & _activeProcessorMask) != 0); + } + + bool IsProcessorValid(BYTE Processor) const + { + return (Processor < _maximumProcessorCount); + } +}; + +class ProcessorNumaInformation +{ +public: + DWORD _ulProcCount; + DWORD _nodeNumber; + vector> _vProcessorMasks; +}; + +class ProcessorCoreInformation +{ +public: + WORD _groupNumber; + KAFFINITY _processorMask; + BYTE _efficiencyClass; + BYTE _groupCoreNumber; + + ProcessorCoreInformation() = delete; + ProcessorCoreInformation( + WORD Group, + KAFFINITY ProcessorMask, + BYTE EfficiencyClass) : + _groupNumber(Group), + _processorMask(ProcessorMask), + _efficiencyClass(EfficiencyClass), + _groupCoreNumber(0) + { + } +}; + +class ProcessorSocketInformation +{ +public: + DWORD _ulProcCount; + DWORD _ulSocketNumber; + vector> _vProcessorMasks; +}; + +class ProcessorTopology +{ +public: + vector _vProcessorGroupInformation; + vector _vProcessorNumaInformation; + vector _vProcessorSocketInformation; + vector _vProcessorCoreInformation; + + DWORD _ulProcessorCount; // total number of (active) processors + BYTE _ubPerformanceEfficiencyClass; // highest performance class present + bool _fSMT; // any SMT cores present + + ProcessorTopology() + { + BOOL fResult; + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX pInformation; + DWORD AllocSize = 1024; + DWORD ReturnedLength = AllocSize; + LOGICAL_PROCESSOR_RELATIONSHIP NumaRelation; + pInformation = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) new char[AllocSize]; + + _ulProcessorCount = 0; + _ubPerformanceEfficiencyClass = 0; + _fSMT = false; + + //// + // Group Relations + //// + + fResult = GetLogicalProcessorInformationEx(RelationGroup, pInformation, &ReturnedLength); + if (!fResult && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + delete [] pInformation; + AllocSize = ReturnedLength; + pInformation = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) new char[AllocSize]; + fResult = GetLogicalProcessorInformationEx(RelationGroup, pInformation, &ReturnedLength); + } + + if (fResult) + { + // Group information comes back as a single (large) element, not an array. + assert(ReturnedLength == pInformation->Size); + + // + // Fill in group topology vector + // + // Note: maximum processor count has no utility other than an indication of the + // bit width of the KAFFINITY mask that might have set values. But: + // + // 1) any mask will be a contiguous run of set bits (no sparse holes); there is + // no case where a 0 bit will be present to indicate a gap/disabled processor + // 2) all system APIs (such as the cpu utilization query) are defined over active + // processors + // + // There are (new?) cases where maximum is represented as > active on large systems, + // which makes these distinctions critical... active processor count is the only + // count that matters. + // + // For the sake of documentation we do save & report out the masks as reported by the + // system, but the only ones we look at are limited to cases where we get information + // in the form of GROUP_AFFINITY, which is just group # and mask (like NUMA and package + // association). + // + + for (WORD i = 0; i < pInformation->Group.ActiveGroupCount; i++) + { + _vProcessorGroupInformation.emplace_back( + i, + pInformation->Group.GroupInfo[i] + ); + + _ulProcessorCount += _vProcessorGroupInformation[i]._activeProcessorCount; + } + } + + //// + // NUMA Relations + //// + + // + // Dynamically detect the available NUMA relations. Non-Ex returns exactly one relation and + // does not define the GroupCount field. Ex scales to return multiple groups for large systems + // with > 64 per NUMA domain and does populate GroupCount. + // + + NumaRelation = RelationNumaNodeEx; + ReturnedLength = AllocSize; + fResult = GetLogicalProcessorInformationEx(NumaRelation, pInformation, &ReturnedLength); + if (!fResult && GetLastError() == ERROR_GEN_FAILURE) + { + NumaRelation = RelationNumaNode; + fResult = GetLogicalProcessorInformationEx(NumaRelation, pInformation, &ReturnedLength); + } + if (!fResult && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + delete [] pInformation; + AllocSize = ReturnedLength; + pInformation = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) new char[AllocSize]; + fResult = GetLogicalProcessorInformationEx(NumaRelation, pInformation, &ReturnedLength); + } + + if (fResult) + { + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX cur = pInformation; + + while (ReturnedLength > 0) + { + ProcessorNumaInformation node; + + assert(ReturnedLength >= cur->Size); + + if (cur->Size > ReturnedLength) + { + break; + } + + node._nodeNumber = cur->NumaNode.NodeNumber; + node._ulProcCount = 0; + for (WORD i = 0; i < (NumaRelation == RelationNumaNode ? 1 : cur->NumaNode.GroupCount); i++) + { + node._ulProcCount += ProcessorTopology::MaskCount(cur->NumaNode.GroupMasks[i].Mask); + node._vProcessorMasks.emplace_back(cur->NumaNode.GroupMasks[i].Group, + cur->NumaNode.GroupMasks[i].Mask); + } + + _vProcessorNumaInformation.push_back(node); + + ReturnedLength -= cur->Size; + cur = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)((PCHAR)cur + cur->Size); + } + } + + //// + // Socket/Package Relations + //// + + ReturnedLength = AllocSize; + fResult = GetLogicalProcessorInformationEx(RelationProcessorPackage, pInformation, &ReturnedLength); + if (!fResult && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + delete [] pInformation; + AllocSize = ReturnedLength; + pInformation = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) new char[AllocSize]; + fResult = GetLogicalProcessorInformationEx(RelationProcessorPackage, pInformation, &ReturnedLength); + } + + if (fResult) + { + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX cur = pInformation; + + DWORD socketNumber = 0; + while (ReturnedLength != 0) + { + ProcessorSocketInformation socket; + + assert(ReturnedLength >= cur->Size); + + if (cur->Size > ReturnedLength) + { + break; + } + + socket._ulProcCount = 0; + socket._ulSocketNumber = socketNumber; + for (WORD i = 0; i < cur->Processor.GroupCount; i++) + { + socket._ulProcCount += ProcessorTopology::MaskCount(cur->Processor.GroupMask[i].Mask); + socket._vProcessorMasks.emplace_back(cur->Processor.GroupMask[i].Group, + cur->Processor.GroupMask[i].Mask); + } + + _vProcessorSocketInformation.push_back(socket); + + ReturnedLength -= cur->Size; + cur = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)((PCHAR)cur + cur->Size); + socketNumber += 1; + } + } + + //// + // Core Relations + //// + + ReturnedLength = AllocSize; + fResult = GetLogicalProcessorInformationEx(RelationProcessorCore, pInformation, &ReturnedLength); + if (!fResult && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + delete [] pInformation; + AllocSize = ReturnedLength; + pInformation = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) new char[AllocSize]; + fResult = GetLogicalProcessorInformationEx(RelationProcessorCore, pInformation, &ReturnedLength); + } + + // + // The EfficiencyClass member was added with Windows 10 + // + + BOOL fEfficiencyClass = false; + if (IsWindows10OrGreater()) + { + fEfficiencyClass = true; + } + + if (fResult) + { + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX cur = pInformation; + BYTE curEfficiency; + + while (ReturnedLength != 0) + { + assert(ReturnedLength >= cur->Size); + + if (cur->Size > ReturnedLength) + { + break; + } + + // + // Determine the highest performance core class and presence of SMT as we sweep. + // Note that SMT is per core and can be asymmetric. + // + + if (fEfficiencyClass) + { + curEfficiency = cur->Processor.EfficiencyClass; + if (_ubPerformanceEfficiencyClass < curEfficiency) + { + _ubPerformanceEfficiencyClass = curEfficiency; + } + } + + if (cur->Processor.Flags & LTP_PC_SMT) + { + _fSMT = true; + } + + assert(pInformation->Processor.GroupCount == 1); + + _vProcessorCoreInformation.emplace_back(cur->Processor.GroupMask[0].Group, + cur->Processor.GroupMask[0].Mask, + fEfficiencyClass ? cur->Processor.EfficiencyClass : (BYTE)0); + + ReturnedLength -= cur->Size; + cur = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)((PCHAR)cur + cur->Size); + } + + // Now guarantee ascending order of group number & cpu mask so that group-relative core number can be assigned + + sort(_vProcessorCoreInformation.begin(), _vProcessorCoreInformation.end(), + [](const ProcessorCoreInformation& a, const ProcessorCoreInformation& b) + { + return a._groupNumber < b._groupNumber || + (a._groupNumber == b._groupNumber && a._processorMask < b._processorMask); + }); + + // Assign group-relative core number + + BYTE coreNumber = 0; + WORD group = 0; + for (auto& core : _vProcessorCoreInformation) + { + if (core._groupNumber != group) + { + group = core._groupNumber; + coreNumber = 0; + } + core._groupCoreNumber = coreNumber++; + } + } + + // TODO: Get the cache relationships as well??? + + delete [] pInformation; + } + + bool IsGroupValid(WORD Group) + { + if (Group < _vProcessorGroupInformation.size()) + { + return true; + } + else + { + return false; + } + } + + // Return the next active processor in the system, exclusive (Next = true) + // or inclusive (Next = false) of the input group/processor. + // Iteration is in order of absolute processor number. + // This does assume at least one core is active, but that is a given. + // + // This logic is strictly unaware that sparse processor masks are not possible; + // address this later, not important. See comments around RelationGroup query. + void GetActiveGroupProcessor(WORD& Group, BYTE& Processor, bool Next) + { + if (Next) + { + Processor++; + } + + while (!_vProcessorGroupInformation[Group].IsProcessorActive(Processor)) + { + if (!_vProcessorGroupInformation[Group].IsProcessorValid(Processor)) + { + Processor = 0; + if (!IsGroupValid(++Group)) + { + Group = 0; + } + } + else + { + Processor++; + } + } + } + + // + // Efficiency of these mappings is not a first order concern. We simply use these to avoid assuming + // ordering of groups/masks of processors within topology structures. There's strictly no reason, + // for example, that socket 0 contains the first groups (0, 1, etc.) of processors, at least not + // documented or guaranteed. + // + + DWORD GetNumaOfProcessor(WORD Group, BYTE Processor) const + { + for (const auto& numa : _vProcessorNumaInformation) + { + for (const auto& mask : numa._vProcessorMasks) + { + if (mask.first == Group && (mask.second & ((KAFFINITY)1 << Processor))) + { + return numa._nodeNumber; + } + } + } + + assert(false); + return 0; + } + + DWORD GetSocketOfProcessor(WORD Group, BYTE Processor) const + { + for (const auto& socket : _vProcessorSocketInformation) + { + for (const auto& mask : socket._vProcessorMasks) + { + if (mask.first == Group && (mask.second & ((KAFFINITY)1 << Processor))) + { + return socket._ulSocketNumber; + } + } + } + + assert(false); + return 0; + } + + BYTE GetCoreOfProcessor(WORD Group, BYTE Processor, BYTE& EfficiencyClass) const + { + for (const auto& core : _vProcessorCoreInformation) + { + if (core._groupNumber == Group && (core._processorMask & ((KAFFINITY)1 << Processor))) + { + EfficiencyClass = core._efficiencyClass; + return core._groupCoreNumber; + } + } + + assert(false); + return 0; + } + + static unsigned int MaskCount(KAFFINITY Mask) + { + // + // Trivial popcount for affinity mask w/o insn dependency + // + + unsigned int count = 0; + + while (Mask) + { + Mask &= (Mask - 1); + count++; + } + + return count; + } +}; + +// +// Helper macros for outputting indented XML. They assume a local variable "indent". +// Use the Inc form when outputting the opening tag for a multi-line section: +// Use Dec for the closing tag: +// + +// start line with indent +#define AddXml(s,str) { (s).append(indent, ' '); (s) += (str); } +// start new indented section +#define AddXmlInc(s,str) { (s).append(indent, ' '); indent += 2; (s) += (str); } +// end indented section +#define AddXmlDec(s,str) { if (indent >= 2) { indent -= 2; }; (s).append(indent, ' '); (s) += (str); } + +class SystemInformation +{ +private: + SYSTEMTIME StartTime; + +public: + ProcessorTopology processorTopology; + string sComputerName; + string sActivePolicyName; + string sActivePolicyGuid; + + SystemInformation() + { + char buffer[128]; + DWORD cb = _countof(buffer); + GUID *guid = NULL; + BOOL fResult; + +#pragma prefast(suppress:38020, "Yes, we're aware this is an ANSI API in a UNICODE project") + fResult = GetComputerNameExA(ComputerNamePhysicalDnsHostname, buffer, &cb); + if (fResult) + { + sComputerName = buffer; + } + + // capture start time + GetSystemTime(&StartTime); + + if (PowerGetActiveScheme(NULL, &guid) == ERROR_SUCCESS && + PowerReadFriendlyName(NULL, guid, NULL, NULL, NULL, &cb) == ERROR_SUCCESS) + { + PUCHAR pwrBuffer; + + if (cb <= _countof(buffer)) + { + pwrBuffer = (PUCHAR) buffer; + } + else + { + pwrBuffer = new UCHAR[cb]; + } + + if (PowerReadFriendlyName(NULL, guid, NULL, NULL, pwrBuffer, &cb) == ERROR_SUCCESS) + { + // Cast wide string down to basic - all of our current output streams are basic + wstring wActivePolicyName = (PWCHAR) pwrBuffer; + std::wstring_convert> cvt; + sActivePolicyName = cvt.to_bytes(wActivePolicyName); + } + + if (pwrBuffer != (PVOID) buffer) + { + delete pwrBuffer; + } + } + + if (sActivePolicyName.empty()) + { + sActivePolicyName = ""; + } + + if (guid) + { + sprintf_s(buffer, _countof(buffer), + "%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + guid->Data1, guid->Data2, guid->Data3, + guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3], + guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]); + + sActivePolicyGuid = buffer; + + LocalFree(guid); + } + } + + // for unit test, squelch variable timestamp + void SystemInformation::ResetTime() + { + StartTime = { 0 }; + } + + string SystemInformation::GetText() const + { + char szBuffer[128]; // guid (36ch), timestamp and power friendly (up to 64ch) + int nWritten; + string sText("System information:\n\n"); + + // identify computer which ran the test + sText += "\tcomputer name: "; + sText += sComputerName; + sText += "\n"; + + sText += "\tstart time: "; + if (StartTime.wYear) { + + nWritten = sprintf_s(szBuffer, _countof(szBuffer), + "%u/%02u/%02u %02u:%02u:%02u UTC", + StartTime.wYear, + StartTime.wMonth, + StartTime.wDay, + StartTime.wHour, + StartTime.wMinute, + StartTime.wSecond); + assert(nWritten && nWritten < _countof(szBuffer)); + sText += szBuffer; + } + + sText += "\n\n\tcpu count:\t\t"; + sText += to_string(processorTopology._ulProcessorCount); + sText += "\n\tcore count:\t\t"; + sText += to_string(processorTopology._vProcessorCoreInformation.size()); + sText += "\n\tgroup count:\t\t"; + sText += to_string(processorTopology._vProcessorGroupInformation.size()); + sText += "\n\tnode count:\t\t"; + sText += to_string(processorTopology._vProcessorNumaInformation.size()); + sText += "\n\tsocket count:\t\t"; + sText += to_string(processorTopology._vProcessorSocketInformation.size()); + sText += "\n\theterogeneous cores:\t"; + sText += processorTopology._ubPerformanceEfficiencyClass ? "y\n" : "n\n"; + + sText += "\n\tactive power scheme:\t"; + sText += sActivePolicyName; + + if (!sActivePolicyGuid.empty()) + { + sText += " ("; + sText += sActivePolicyGuid; + sText += ")"; + } + + sText += "\n"; + + return sText; + } + + string SystemInformation::GetXml(UINT32 indent) const + { + char szBuffer[64]; // enough for 64bit mask (17ch) and timestamp + int nWritten; + string sXml; + + AddXmlInc(sXml, "\n"); + + // identify computer which ran the test + AddXml(sXml, ""); + sXml += sComputerName; + sXml += "\n"; + + // identify tool version which performed the test + AddXmlInc(sXml, "\n"); + AddXml(sXml,"" DISKSPD_NUMERIC_VERSION_STRING "\n"); + AddXml(sXml, "" DISKSPD_DATE_VERSION_STRING "\n"); + AddXmlDec(sXml, "\n"); + + AddXml(sXml, ""); + if (StartTime.wYear) { + + nWritten = sprintf_s(szBuffer, _countof(szBuffer), + "%u/%02u/%02u %02u:%02u:%02u UTC", + StartTime.wYear, + StartTime.wMonth, + StartTime.wDay, + StartTime.wHour, + StartTime.wMinute, + StartTime.wSecond); + assert(nWritten && nWritten < _countof(szBuffer)); + sXml += szBuffer; + } + sXml += "\n"; + + AddXml(sXml, "\n"; + + // processor topology + AddXmlInc(sXml, "\n" : "false\">\n"; + + for (const auto& g : processorTopology._vProcessorGroupInformation) + { + AddXml(sXml, "\n"; + + } + for (const auto& n : processorTopology._vProcessorNumaInformation) + { + AddXmlInc(sXml, "\n"; + for (const auto& g : n._vProcessorMasks) + { + AddXml(sXml, "\n"; + } + AddXmlDec(sXml, "\n"); + } + for (const auto& s : processorTopology._vProcessorSocketInformation) + { + AddXmlInc(sXml, "\n"; + for (const auto& g : s._vProcessorMasks) + { + AddXml(sXml, "\n"; + } + AddXmlDec(sXml, "\n"); + } + for (const auto& h : processorTopology._vProcessorCoreInformation) + { + AddXml(sXml, "\n"; + } + + AddXmlDec(sXml, "\n"); + AddXmlDec(sXml, "\n"); + + return sXml; + } +}; + +extern SystemInformation g_SystemInformation; + +struct Synchronization +{ + ULONG ulStructSize; //size of the structure that the caller is aware of (to easier achieve backward compatibility in a future) + HANDLE hStopEvent; //an event to be signalled if the scenario is to be stop before time ellapses + HANDLE hStartEvent; //an event for signalling start + CALLBACK_TEST_STARTED pfnCallbackTestStarted; //a function to be called if the measured test is about to start + CALLBACK_TEST_FINISHED pfnCallbackTestFinished; //a function to be called as soon as the measrued test finishes +}; + +#define STRUCT_SYNCHRONIZATION_SUPPORTS(pSynch, Field) ( \ + (NULL != (pSynch)) && \ + ((pSynch)->ulStructSize >= offsetof(struct Synchronization, Field) + sizeof((pSynch)->Field)) \ + ) + +// caching modes +// cached -> default (-Sb explicitly) +// disableoscache -> no_intermediate_buffering (-S or -Su) +// disablelocalcache -> cached, but then tear down local rdr cache (-Sr) +enum class TargetCacheMode { + Undefined = 0, + Cached, + DisableOSCache, + DisableLocalCache +}; + +// writethrough modes +// off -> default +// on -> (-Sw or implied with -Sh == -Suw/-Swu) +enum class WriteThroughMode { + Undefined = 0, + Off, + On, +}; + +// memory mapped IO modes +// off -> default +// on -> (-Sm or -Smw) +enum class MemoryMappedIoMode { + Undefined = 0, + Off, + On, +}; + +// memory mapped IO flush modes +// off / Undefined -> default +// on -> (-Sm or -Smw) +enum class MemoryMappedIoFlushMode { + Undefined = 0, + ViewOfFile, + NonVolatileMemory, + NonVolatileMemoryNoDrain, +}; + +enum class IOMode +{ + Unknown, + Random, + Sequential, + Mixed, + InterlockedSequential, + ParallelAsync +}; + +class ThreadTarget +{ +public: + + ThreadTarget() : + _ulThread(0xFFFFFFFF), + _ulWeight(0) + { + } + + void SetThread(UINT32 ulThread) { _ulThread = ulThread; } + UINT32 GetThread() const { return _ulThread; } + + void SetWeight(UINT32 ulWeight) { _ulWeight = ulWeight; } + UINT32 GetWeight() const { return _ulWeight; } + + string GetXml(UINT32 indent) const; + +private: + UINT32 _ulThread; + UINT32 _ulWeight; +}; + +// Character which leads off a template target definition; e.g. *1, *2 +#define TEMPLATE_TARGET_PREFIX ('*') + +class Target +{ +public: + + Target() : + _dwBlockSize(64 * 1024), + _dwRequestCount(2), + _ullBlockAlignment(0), + _ulWriteRatio(0), + _ulRandomRatio(0), + _ullBaseFileOffset(0), + _fParallelAsyncIO(false), + _fInterlockedSequential(false), + _cacheMode(TargetCacheMode::Cached), + _writeThroughMode(WriteThroughMode::Off), + _memoryMappedIoMode(MemoryMappedIoMode::Off), + _memoryMappedIoNvToken(nullptr), + _memoryMappedIoFlushMode(MemoryMappedIoFlushMode::Undefined), + _fZeroWriteBuffers(false), + _dwThreadsPerFile(1), + _ullThreadStride(0), + _fCreateFile(false), + _fPrecreated(false), + _ullFileSize(0), + _ullMaxFileSize(0), + _fUseBurstSize(false), + _dwBurstSize(0), + _dwThinkTime(0), + _fThinkTime(false), + _fSequentialScanHint(false), + _fRandomAccessHint(false), + _fTemporaryFileHint(false), + _fUseLargePages(false), + _mappedViewFileHandle(INVALID_HANDLE_VALUE), + _mappedView(NULL), + _ioPriorityHint(IoPriorityHintNormal), + _ulWeight(1), + _dwThroughputBytesPerMillisecond(0), + _dwThroughputIOPS(0), + _cbRandomDataWriteBuffer(0), + _sRandomDataWriteBufferSourcePath(), + _pRandomDataWriteBuffer(nullptr), + _distributionType(DistributionType::None) + { + } + + IOMode GetIOMode() const + { + if (GetRandomRatio() == 100) + { + return IOMode::Random; + } + else if (GetRandomRatio() != 0) + { + return IOMode::Mixed; + } + else if (GetUseParallelAsyncIO()) + { + return IOMode::ParallelAsync; + } + else if (GetUseInterlockedSequential()) + { + return IOMode::InterlockedSequential; + } + else + { + return IOMode::Sequential; + } + } + + void SetPath(const string& sPath) { _sPath = sPath; } + void SetPath(const char *pPath) { _sPath = pPath; } + const string& GetPath() const { return _sPath; } + + void SetBlockSizeInBytes(DWORD dwBlockSize) { _dwBlockSize = dwBlockSize; } + DWORD GetBlockSizeInBytes() const { return _dwBlockSize; } + + void SetBlockAlignmentInBytes(UINT64 ullBlockAlignment) + { + _ullBlockAlignment = ullBlockAlignment; + } + // actual is used in validation to detect unclear/mis-specified intent + // like -rs -s + UINT64 GetBlockAlignmentInBytes(bool actual = false) const + { + return _ullBlockAlignment ? _ullBlockAlignment : (actual ? 0 : _dwBlockSize); + } + + void SetWriteRatio(UINT32 writeRatio) { _ulWriteRatio = writeRatio; } + UINT32 GetWriteRatio() const { return _ulWriteRatio; } + + void SetRandomRatio(UINT32 randomRatio) { _ulRandomRatio = randomRatio; } + UINT32 GetRandomRatio() const { return _ulRandomRatio; } + + void SetBaseFileOffsetInBytes(UINT64 ullBaseFileOffset) { _ullBaseFileOffset = ullBaseFileOffset; } + UINT64 GetBaseFileOffsetInBytes() const { return _ullBaseFileOffset; } + UINT64 GetThreadBaseRelativeOffsetInBytes(UINT32 ulThreadNo) const { return ulThreadNo * _ullThreadStride; } + UINT64 GetThreadBaseFileOffsetInBytes(UINT32 ulThreadNo) const { return _ullBaseFileOffset + GetThreadBaseRelativeOffsetInBytes(ulThreadNo); } + + + void SetSequentialScanHint(bool fBool) { _fSequentialScanHint = fBool; } + bool GetSequentialScanHint() const { return _fSequentialScanHint; } + + void SetRandomAccessHint(bool fBool) { _fRandomAccessHint = fBool; } + bool GetRandomAccessHint() const { return _fRandomAccessHint; } + + void SetTemporaryFileHint(bool fBool) { _fTemporaryFileHint = fBool; } + bool GetTemporaryFileHint() const { return _fTemporaryFileHint; } + + void SetUseLargePages(bool fBool) { _fUseLargePages = fBool; } + bool GetUseLargePages() const { return _fUseLargePages; } + + void SetRequestCount(DWORD dwRequestCount) { _dwRequestCount = dwRequestCount; } + DWORD GetRequestCount() const { return _dwRequestCount; } + + void SetCacheMode(TargetCacheMode cacheMode) { _cacheMode = cacheMode; } + TargetCacheMode GetCacheMode() const { return _cacheMode; } + + void SetWriteThroughMode(WriteThroughMode writeThroughMode ) { _writeThroughMode = writeThroughMode; } + WriteThroughMode GetWriteThroughMode() const { return _writeThroughMode; } + + void SetMemoryMappedIoMode(MemoryMappedIoMode memoryMappedIoMode ) { _memoryMappedIoMode = memoryMappedIoMode; } + MemoryMappedIoMode GetMemoryMappedIoMode() const { return _memoryMappedIoMode; } + + void SetMemoryMappedIoNvToken(PVOID memoryMappedIoNvToken) { _memoryMappedIoNvToken = memoryMappedIoNvToken; } + PVOID GetMemoryMappedIoNvToken() const { return _memoryMappedIoNvToken; } + + void SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode memoryMappedIoFlushMode) { _memoryMappedIoFlushMode = memoryMappedIoFlushMode; } + MemoryMappedIoFlushMode GetMemoryMappedIoFlushMode() const { return _memoryMappedIoFlushMode; } + + void SetZeroWriteBuffers(bool fBool) { _fZeroWriteBuffers = fBool; } + bool GetZeroWriteBuffers() const { return _fZeroWriteBuffers; } + + void SetRandomDataWriteBufferSize(UINT64 cbWriteBuffer) { _cbRandomDataWriteBuffer = cbWriteBuffer; } + UINT64 GetRandomDataWriteBufferSize(void) const { return _cbRandomDataWriteBuffer; } + + void SetRandomDataWriteBufferSourcePath(string sPath) { _sRandomDataWriteBufferSourcePath = sPath; } + string GetRandomDataWriteBufferSourcePath() const { return _sRandomDataWriteBufferSourcePath; } + + void SetUseBurstSize(bool fBool) { _fUseBurstSize = fBool; } + bool GetUseBurstSize() const { return _fUseBurstSize; } + + void SetBurstSize(DWORD dwBurstSize) { _dwBurstSize = dwBurstSize; } + DWORD GetBurstSize() const { return _dwBurstSize; } + + void SetThinkTime(DWORD dwThinkTime) { _dwThinkTime = dwThinkTime; } + DWORD GetThinkTime() const { return _dwThinkTime; } + + void SetEnableThinkTime(bool fBool) { _fThinkTime = fBool; } + bool GetEnableThinkTime() const { return _fThinkTime; } + + void SetThreadsPerFile(DWORD dwThreadsPerFile) { _dwThreadsPerFile = dwThreadsPerFile; } + DWORD GetThreadsPerFile() const { return _dwThreadsPerFile; } + + void SetCreateFile(bool fBool) { _fCreateFile = fBool; } + bool GetCreateFile() const { return _fCreateFile; } + + void SetFileSize(UINT64 ullFileSize) { _ullFileSize = ullFileSize; } + UINT64 GetFileSize() const { return _ullFileSize; } // TODO: InBytes + + void SetMaxFileSize(UINT64 ullMaxFileSize) { _ullMaxFileSize = ullMaxFileSize; } + UINT64 GetMaxFileSize() const { return _ullMaxFileSize; } + + void SetUseParallelAsyncIO(bool fBool) { _fParallelAsyncIO = fBool; } + bool GetUseParallelAsyncIO() const { return _fParallelAsyncIO; } + + void SetUseInterlockedSequential(bool fBool) { _fInterlockedSequential = fBool; } + bool GetUseInterlockedSequential() const { return _fInterlockedSequential; } + + void SetThreadStrideInBytes(UINT64 ullThreadStride) { _ullThreadStride = ullThreadStride; } + UINT64 GetThreadStrideInBytes() const { return _ullThreadStride; } + + void SetMappedViewFileHandle(HANDLE FileHandle) { _mappedViewFileHandle = FileHandle; } + HANDLE GetMappedViewFileHandle() const { return _mappedViewFileHandle; } + + void SetMappedView(BYTE *MappedView) { _mappedView = MappedView; } + BYTE* GetMappedView() const { return _mappedView; } + + void SetIOPriorityHint(PRIORITY_HINT _hint) + { + assert(_hint < MaximumIoPriorityHintType); + _ioPriorityHint = _hint; + } + PRIORITY_HINT GetIOPriorityHint() const { return _ioPriorityHint; } + + void SetWeight(UINT32 ulWeight) { _ulWeight = ulWeight; } + UINT32 GetWeight() const { return _ulWeight; } + + void AddThreadTarget(const ThreadTarget &threadTarget) + { + _vThreadTargets.push_back(threadTarget); + } + vector GetThreadTargets() const { return _vThreadTargets; } + + void SetPrecreated(bool fBool) { _fPrecreated = fBool; } + bool GetPrecreated() const { return _fPrecreated; } + + // Convert units to BPMS. Nonzero value of IOPS indicates originally specified units for display/profile. + void SetThroughputIOPS(DWORD dwIOPS) + { + _dwThroughputIOPS = dwIOPS; + _dwThroughputBytesPerMillisecond = (dwIOPS * _dwBlockSize) / 1000; + } + DWORD GetThroughputIOPS() const { return _dwThroughputIOPS; } + void SetThroughput(DWORD dwThroughputBytesPerMillisecond) + { + _dwThroughputIOPS = 0; + _dwThroughputBytesPerMillisecond = dwThroughputBytesPerMillisecond; + } + DWORD GetThroughputInBytesPerMillisecond() const { return _dwThroughputBytesPerMillisecond; } + + string GetXml(UINT32 indent) const; + + bool AllocateAndFillRandomDataWriteBuffer(Random *pRand); + void FreeRandomDataWriteBuffer(); + BYTE* GetRandomDataWriteBuffer(Random *pRand); + + void SetDistributionRange(const vector& v, DistributionType t) + { + _vDistributionRange = v; _distributionType = t; + + // Now place final element if IO% is < 100. + // If this is an absolute specification, it will map to zero length here and + // conversion will occur at the time of target open to the rest of the target. + // For the percent specification we place the final element as-if directly stated, + // consuming the tail length. + // + // This done here so that the stated specification is indeed complete, and not left + // for the effective distribution. + // + // TBD this should be moved to a proper Distribution class. + + const DistributionRange& last = *_vDistributionRange.rbegin(); + + UINT32 ioCur = last._src + last._span; + if (ioCur < 100) + { + UINT64 targetCur = last._dst.first + last._dst.second; + if (t == DistributionType::Percent && targetCur < 100) + { + // tail is available + // if tail is not available, this will be caught by validation + _vDistributionRange.emplace_back(ioCur, 100 - ioCur, make_pair(targetCur, 100 - targetCur)); + } + else + { + _vDistributionRange.emplace_back(ioCur, 100 - ioCur, make_pair(targetCur, 0)); + } + } + } + auto& GetDistributionRange() const { return _vDistributionRange; } + auto GetDistributionType() const { return _distributionType; } + + DWORD GetCreateFlags(bool fAsync) + { + DWORD dwFlags = FILE_ATTRIBUTE_NORMAL; + + if (GetSequentialScanHint()) + { + dwFlags |= FILE_FLAG_SEQUENTIAL_SCAN; + } + + if (GetRandomAccessHint()) + { + dwFlags |= FILE_FLAG_RANDOM_ACCESS; + } + + if (GetTemporaryFileHint()) + { + dwFlags |= FILE_ATTRIBUTE_TEMPORARY; + } + + if (fAsync) + { + dwFlags |= FILE_FLAG_OVERLAPPED; + } + + if (GetCacheMode() == TargetCacheMode::DisableOSCache) + { + dwFlags |= FILE_FLAG_NO_BUFFERING; + } + + if (GetWriteThroughMode( ) == WriteThroughMode::On) + { + dwFlags |= FILE_FLAG_WRITE_THROUGH; + } + + return dwFlags; + } + +private: + string _sPath; + DWORD _dwBlockSize; + DWORD _dwRequestCount; // TODO: change the name to something more descriptive (OutstandingRequestCount?) + + UINT64 _ullBlockAlignment; + UINT32 _ulWriteRatio; + UINT32 _ulRandomRatio; + + UINT64 _ullBaseFileOffset; + + TargetCacheMode _cacheMode; + WriteThroughMode _writeThroughMode; + MemoryMappedIoMode _memoryMappedIoMode; + MemoryMappedIoFlushMode _memoryMappedIoFlushMode; + PVOID _memoryMappedIoNvToken; + DWORD _dwThreadsPerFile; + UINT64 _ullThreadStride; + + UINT64 _ullFileSize; + UINT64 _ullMaxFileSize; + + DWORD _dwBurstSize; // number of IOs in a burst + DWORD _dwThinkTime; // time to pause before issuing the next burst of IOs + + DWORD _dwThroughputBytesPerMillisecond; // set to 0 to disable throttling + DWORD _dwThroughputIOPS; // if IOPS are specified they are converted to BPMS but saved for fidelity to XML/output + + bool _fThinkTime:1; // variable to decide whether to think between IOs (default is false) (removed by using _dwThinkTime==0?) + bool _fUseBurstSize:1; // TODO: "use" or "enable"?; since burst size must be specified with the think time, one variable should be sufficient + bool _fZeroWriteBuffers:1; + bool _fCreateFile:1; + bool _fPrecreated:1; // used to track which files have been created before the first timespan and which have to be created later + bool _fParallelAsyncIO:1; + bool _fInterlockedSequential:1; + bool _fSequentialScanHint:1; // open file with the FILE_FLAG_SEQUENTIAL_SCAN hint + bool _fRandomAccessHint:1; // open file with the FILE_FLAG_RANDOM_ACCESS hint + bool _fTemporaryFileHint:1; // open file with the FILE_ATTRIBUTE_TEMPORARY hint + bool _fUseLargePages:1; // Use large pages for IO buffers + + UINT64 _cbRandomDataWriteBuffer; // if > 0, then the write buffer should be filled with random data + string _sRandomDataWriteBufferSourcePath; // file that should be used for filling the write buffer (if the path is not available, use a crypto provider) + BYTE *_pRandomDataWriteBuffer; // a buffer used for write data when _cbWriteBuffer > 0; it's shared by all the threads working on this target + + HANDLE _mappedViewFileHandle; + BYTE *_mappedView; + + PRIORITY_HINT _ioPriorityHint; + + UINT32 _ulWeight; + vector _vThreadTargets; + + vector _vDistributionRange; + DistributionType _distributionType; + + bool _FillRandomDataWriteBuffer(Random *pRand); + + friend class UnitTests::ProfileUnitTests; + friend class UnitTests::TargetUnitTests; +}; + +class AffinityAssignment +{ +public: + WORD wGroup; + BYTE bProc; + + AffinityAssignment() = delete; + AffinityAssignment(WORD p_wGroup, BYTE p_bProc) : + wGroup(p_wGroup), + bProc(p_bProc) + { + } +}; + +class TimeSpan +{ +public: + TimeSpan() : + _ulDuration(10), + _ulWarmUp(5), + _ulCoolDown(0), + _ulRandSeed(0), + _dwThreadCount(0), + _dwRequestCount(0), + _fRandomWriteData(false), + _fDisableAffinity(false), + _fCompletionRoutines(false), + _fMeasureLatency(false), + _fCalculateIopsStdDev(false), + _ulIoBucketDurationInMilliseconds(1000) + { + } + + void ClearAffinityAssignment() + { + _vAffinity.clear(); + } + void AddAffinityAssignment(WORD wGroup, BYTE bProc) + { + _vAffinity.emplace_back(wGroup, bProc); + } + const auto& GetAffinityAssignments() const { return _vAffinity; } + + void AddTarget(const Target& target) + { + _vTargets.push_back(Target(target)); + } + + vector GetTargets() const { return _vTargets; } + + void SetDuration(UINT32 ulDuration) { _ulDuration = ulDuration; } + UINT32 GetDuration() const { return _ulDuration; } + + void SetWarmup(UINT32 ulWarmup) { _ulWarmUp = ulWarmup; } + UINT32 GetWarmup() const { return _ulWarmUp; } + + void SetCooldown(UINT32 ulCooldown) { _ulCoolDown = ulCooldown; } + UINT32 GetCooldown() const { return _ulCoolDown; } + + void SetRandSeed(UINT32 ulRandSeed) { _ulRandSeed = ulRandSeed; } + UINT32 GetRandSeed() const { return _ulRandSeed; } + + void SetRandomWriteData(bool fRandomWriteData) { _fRandomWriteData = fRandomWriteData; } + bool GetRandomWriteData() const { return _fRandomWriteData; } + + void SetThreadCount(DWORD dwThreadCount) { _dwThreadCount = dwThreadCount; } + DWORD GetThreadCount() const { return _dwThreadCount; } + + void SetRequestCount(DWORD dwRequestCount) { _dwRequestCount = dwRequestCount; } + DWORD GetRequestCount() const { return _dwRequestCount; } + + void SetDisableAffinity(bool fDisableAffinity) { _fDisableAffinity = fDisableAffinity; } + bool GetDisableAffinity() const { return _fDisableAffinity; } + + void SetCompletionRoutines(bool fCompletionRoutines) { _fCompletionRoutines = fCompletionRoutines; } + bool GetCompletionRoutines() const { return _fCompletionRoutines; } + + void SetMeasureLatency(bool fMeasureLatency) { _fMeasureLatency = fMeasureLatency; } + bool GetMeasureLatency() const { return _fMeasureLatency; } + + void SetCalculateIopsStdDev(bool fCalculateStdDev) { _fCalculateIopsStdDev = fCalculateStdDev; } + bool GetCalculateIopsStdDev() const { return _fCalculateIopsStdDev; } + + void SetIoBucketDurationInMilliseconds(UINT32 ulIoBucketDurationInMilliseconds) { _ulIoBucketDurationInMilliseconds = ulIoBucketDurationInMilliseconds; } + UINT32 GetIoBucketDurationInMilliseconds() const { return _ulIoBucketDurationInMilliseconds; } + + string GetXml(UINT32 indent) const; + void MarkFilesAsPrecreated(const vector vFiles); + +private: + vector _vTargets; + UINT32 _ulDuration; + UINT32 _ulWarmUp; + UINT32 _ulCoolDown; + UINT32 _ulRandSeed; + DWORD _dwThreadCount; + DWORD _dwRequestCount; + bool _fRandomWriteData; + bool _fDisableAffinity; + vector _vAffinity; + bool _fCompletionRoutines; + bool _fMeasureLatency; + bool _fCalculateIopsStdDev; + UINT32 _ulIoBucketDurationInMilliseconds; + + friend class UnitTests::ProfileUnitTests; +}; + +enum class ResultsFormat +{ + Text, + Xml +}; + +enum class PrecreateFiles +{ + None, + UseMaxSize, + OnlyFilesWithConstantSizes, + OnlyFilesWithConstantOrZeroSizes +}; + +class Profile +{ +public: + Profile() : + _fProfileOnly(false), + _fVerbose(false), + _fVerboseStats(false), + _dwProgress(0), + _fEtwEnabled(false), + _fEtwProcess(false), + _fEtwThread(false), + _fEtwImageLoad(false), + _fEtwDiskIO(false), + _fEtwMemoryPageFaults(false), + _fEtwMemoryHardFaults(false), + _fEtwNetwork(false), + _fEtwRegistry(false), + _fEtwUsePagedMemory(false), + _fEtwUsePerfTimer(false), + _fEtwUseSystemTimer(false), + _fEtwUseCyclesCounter(false), + _resultsFormat(ResultsFormat::Text), + _precreateFiles(PrecreateFiles::None) + { + } + + void ClearTimeSpans() + { + _vTimeSpans.clear(); + } + + void AddTimeSpan(const TimeSpan& timeSpan) + { + _vTimeSpans.push_back(TimeSpan(timeSpan)); + } + + const vector& GetTimeSpans() const { return _vTimeSpans; } + + void SetProfileOnly(bool b) { _fProfileOnly = b; } + bool GetProfileOnly() const { return _fProfileOnly; } + + void SetVerbose(bool b) { _fVerbose = b; } + bool GetVerbose() const { return _fVerbose; } + + void SetVerboseStats(bool b) { _fVerboseStats = b; } + bool GetVerboseStats() const { return _fVerboseStats; } + + void SetProgress(DWORD dwProgress) { _dwProgress = dwProgress; } + DWORD GetProgress() const { return _dwProgress; } + + void SetCmdLine(string sCmdLine) { _sCmdLine = sCmdLine; } + string GetCmdLine() const { return _sCmdLine; }; + + void SetResultsFormat(ResultsFormat format) { _resultsFormat = format; } + ResultsFormat GetResultsFormat() const { return _resultsFormat; } + + void SetPrecreateFiles(PrecreateFiles c) { _precreateFiles = c; } + PrecreateFiles GetPrecreateFiles() const { return _precreateFiles; } + + //ETW + void SetEtwEnabled(bool b) { _fEtwEnabled = b; } + void SetEtwProcess(bool b) { _fEtwProcess = b; } + void SetEtwThread(bool b) { _fEtwThread = b; } + void SetEtwImageLoad(bool b) { _fEtwImageLoad = b; } + void SetEtwDiskIO(bool b) { _fEtwDiskIO = b; } + void SetEtwMemoryPageFaults(bool b) { _fEtwMemoryPageFaults = b; } + void SetEtwMemoryHardFaults(bool b) { _fEtwMemoryHardFaults = b; } + void SetEtwNetwork(bool b) { _fEtwNetwork = b; } + void SetEtwRegistry(bool b) { _fEtwRegistry = b; } + void SetEtwUsePagedMemory(bool b) { _fEtwUsePagedMemory = b; } + void SetEtwUsePerfTimer(bool b) { _fEtwUsePerfTimer = b; } + void SetEtwUseSystemTimer(bool b) { _fEtwUseSystemTimer = b; } + void SetEtwUseCyclesCounter(bool b) { _fEtwUseCyclesCounter = b; } + + bool GetEtwEnabled() const { return _fEtwEnabled; } + bool GetEtwProcess() const { return _fEtwProcess; } + bool GetEtwThread() const { return _fEtwThread; } + bool GetEtwImageLoad() const { return _fEtwImageLoad; } + bool GetEtwDiskIO() const { return _fEtwDiskIO; } + bool GetEtwMemoryPageFaults() const { return _fEtwMemoryPageFaults; } + bool GetEtwMemoryHardFaults() const { return _fEtwMemoryHardFaults; } + bool GetEtwNetwork() const { return _fEtwNetwork; } + bool GetEtwRegistry() const { return _fEtwRegistry; } + bool GetEtwUsePagedMemory() const { return _fEtwUsePagedMemory; } + bool GetEtwUsePerfTimer() const { return _fEtwUsePerfTimer; } + bool GetEtwUseSystemTimer() const { return _fEtwUseSystemTimer; } + bool GetEtwUseCyclesCounter() const { return _fEtwUseCyclesCounter; } + + string GetXml(UINT32 indent) const; + bool Validate(bool fSingleSpec, SystemInformation *pSystem = nullptr) const; + void MarkFilesAsPrecreated(const vector vFiles); + +private: + Profile(const Profile& T); + + vector_vTimeSpans; + bool _fVerbose; + bool _fVerboseStats; + bool _fProfileOnly; + DWORD _dwProgress; + string _sCmdLine; + ResultsFormat _resultsFormat; + PrecreateFiles _precreateFiles; + + //ETW + bool _fEtwEnabled; + bool _fEtwProcess; + bool _fEtwThread; + bool _fEtwImageLoad; + bool _fEtwDiskIO; + bool _fEtwMemoryPageFaults; + bool _fEtwMemoryHardFaults; + bool _fEtwNetwork; + bool _fEtwRegistry; + bool _fEtwUsePagedMemory; + bool _fEtwUsePerfTimer; + bool _fEtwUseSystemTimer; + bool _fEtwUseCyclesCounter; + + friend class UnitTests::ProfileUnitTests; +}; + +class IORequest +{ +public: + IORequest(Random *pRand) : + _ioType(IOOperation::ReadIO), + _pRand(pRand), + _iCurrentTarget(0), + _ullStartTime(0), + _ulRequestIndex(0xFFFFFFFF), + _ullTotalWeight(0), + _fEqualWeights(true), + _ActivityId() + { + memset(&_overlapped, 0, sizeof(OVERLAPPED)); + } + + static IORequest *OverlappedToIORequest(OVERLAPPED *pOverlapped) + { + return CONTAINING_RECORD(pOverlapped, IORequest, _overlapped); + } + + OVERLAPPED *GetOverlapped() { return &_overlapped; } + + void AddTarget(Target *pTarget, UINT32 ulWeight) + { + _vTargets.push_back(pTarget); + _vulTargetWeights.push_back(ulWeight); + _ullTotalWeight += ulWeight; + + if (ulWeight != _vulTargetWeights[0]) { + _fEqualWeights = false; + } + } + + Target *GetCurrentTarget() { return _vTargets[_iCurrentTarget]; } + size_t GetCurrentTargetIndex() { return _iCurrentTarget; } + + Target *GetNextTarget() + { + UINT64 ullWeight; + + if (_vTargets.size() == 1) { + _iCurrentTarget = 0; + } + else if (_fEqualWeights) { + _iCurrentTarget = _pRand->Rand32() % _vTargets.size(); + } + else { + ullWeight = _pRand->Rand64() % _ullTotalWeight; + + for (size_t iTarget = 0; iTarget < _vTargets.size(); iTarget++) { + if (ullWeight < _vulTargetWeights[iTarget]) { + _iCurrentTarget = iTarget; + break; + } + + ullWeight -= _vulTargetWeights[iTarget]; + } + } + + return GetCurrentTarget(); + } + + void SetIoType(IOOperation ioType) { _ioType = ioType; } + IOOperation GetIoType() const { return _ioType; } + + void SetStartTime(UINT64 ullStartTime) { _ullStartTime = ullStartTime; } + UINT64 GetStartTime() const { return _ullStartTime; } + + void SetRequestIndex(UINT32 ulRequestIndex) { _ulRequestIndex = ulRequestIndex; } + UINT32 GetRequestIndex() const { return _ulRequestIndex; } + + void SetActivityId(GUID ActivityId) { _ActivityId = ActivityId; } + GUID GetActivityId() const { return _ActivityId; } + +private: + OVERLAPPED _overlapped; + vector _vTargets; + vector _vulTargetWeights; + UINT64 _ullTotalWeight; + bool _fEqualWeights; + Random *_pRand; + size_t _iCurrentTarget; + IOOperation _ioType; + UINT64 _ullStartTime; + UINT32 _ulRequestIndex; + GUID _ActivityId; +}; + +typedef struct _ACTIVITY_ID { + UINT32 Thread; + UINT32 Reserved; + UINT64 Count; +} ACTIVITY_ID; + +C_ASSERT(sizeof(ACTIVITY_ID) == sizeof(GUID)); + +// Forward declaration +class ThreadTargetState; + +class ThreadParameters +{ +public: + ThreadParameters() : + pProfile(nullptr), + pTimeSpan(nullptr), + pullSharedSequentialOffsets(nullptr), + ulRandSeed(0), + ulThreadNo(0), + ulRelativeThreadNo(0) + { + } + + const Profile *pProfile; + const TimeSpan *pTimeSpan; + + vector vTargets; + vector vTargetStates; + vector vhTargets; + + vector vulReadBufferSize; + vector vpDataBuffers; + vector vIORequest; + vector vThroughputMeters; + + // For interlocked sequential access (-si): + // Pointers to offsets shared between threads, incremented with an interlocked op + UINT64* pullSharedSequentialOffsets; + + Random *pRand; + + UINT32 ulRandSeed; + UINT32 ulThreadNo; + UINT32 ulRelativeThreadNo; + + // accounting + volatile bool *pfAccountingOn; + PUINT64 pullStartTime; + ThreadResults *pResults; + + //progress dots + DWORD dwIOCnt; + + //group affinity + WORD wGroupNum; + DWORD bProcNum; + + HANDLE hStartEvent; + + // TODO: check how it's used + HANDLE hEndEvent; //used only in case of completion routines (not for IO Completion Ports) + + bool AllocateAndFillBufferForTarget(const Target& target); + BYTE* GetReadBuffer(size_t iTarget, size_t iRequest); + BYTE* GetWriteBuffer(size_t iTarget, size_t iRequest); + DWORD GetTotalRequestCount() const; + bool InitializeMappedViewForTarget(Target& target, DWORD DesiredAccess); + + GUID NextActivityId() + { + GUID ActivityId; + ACTIVITY_ID* ActivityGuid = (ACTIVITY_ID*)&ActivityId; + + ActivityGuid->Thread = ulThreadNo; + ActivityGuid->Reserved = 0; + // The count is byte swapped so it's understandable in a trace. + ActivityGuid->Count = _byteswap_uint64(++_ullActivityCount); + + return ActivityId; + } + +private: + ThreadParameters(const ThreadParameters& T); + UINT64 _ullActivityCount; +}; + +class ThreadTargetState +{ + public: + + ThreadTargetState( + const ThreadParameters *pTp, + size_t iTarget, + UINT64 targetSize + ) : + _tp(pTp), + _target(&_tp->vTargets[iTarget]), + _targetSize(targetSize), + _mode(_target->GetIOMode()), + + _nextSeqOffset(0), + _lastIO(IOOperation::Unknown), + _sharedSeqOffset(nullptr), + _ioDistributionSpan(100) + { + // + // Now calculate the maximum base-relative file offset that IO can be issued at. + // + // Trim by max file size limit, and reduce by base file offset. + // + + if (_target->GetMaxFileSize()) + { + _relTargetSize = _targetSize > _target->GetMaxFileSize() ? _target->GetMaxFileSize() : _targetSize; + } + else + { + _relTargetSize = _targetSize; + } + + _relTargetSize -= _target->GetBaseFileOffsetInBytes(); + + // + // Align relative to the maximum offset at which aligned IO could be issued at. + // + + _relTargetSizeAligned = _relTargetSize - _target->GetBlockSizeInBytes(); + _relTargetSizeAligned -= _relTargetSizeAligned % _target->GetBlockAlignmentInBytes(); + _relTargetSizeAligned += _target->GetBlockAlignmentInBytes(); + + // Grab the shared sequential pointer if this is interlocked. + + if (_mode == IOMode::InterlockedSequential) + { + assert(_tp->pullSharedSequentialOffsets != nullptr); + _sharedSeqOffset = &_tp->pullSharedSequentialOffsets[iTarget]; + } + + // Convert and finalize the random distribution stated in the target using final bounds. + + switch (_target->GetDistributionType()) + { + case DistributionType::Percent: + { + UINT32 ioCarry = 0; + + for (auto& r : _target->GetDistributionRange()) + { + // + // The basic premise is to align the range's bounds to discover whether there are + // any aligned offsets within it. To do this we align DOWN. This moves the adjacent + // end of this range and base of the next in lockstep. + // + // There are two basic branches and three subcases in each: + // + // * aligned base + // * unaligned base + // * and within each + // * aligned end + // * unaligned end in same alignment unit + // * unaligned end in next/following alignment unit + // + // * aligned/aligned will not move b/e, there will be a positive range + // * aligned/unaligned-next will move e in step with the following b + // and there will be a positive range + // * aligned/unaligned-same will result in b=e after aligning; IO at b is + // the only possible IO + // + // Unaligned base is more interesting due to degenerate spans, spans where the + // mimimum %range is smaller than the block alignment. For instance, a 100KiB target + // with a 4K alignment has a 1%/1KB minimum and may create these cases. + // + // * unaligned/aligned aligns base (down) and there is a positive range + // * unaligned/unaligned-next aligns both down and there is a positive range + // * unaligned/unaligned-same has no aligned offset in the range; we can detect + // this by aligning e first and seeing if it is less than unaligned b. there + // are two subcases: + // * if the prior range is of zero length, we roll this range's IO% onto it - + // this combines two or more adjacent degenerate spans + // * if it was not of zero length, we roll over the IO% to the next/last range + // + // Now, in the cases where we have a positive range we may still find our aligned + // base is the same as the prior range - the prior was degenerate and the current + // is not. In this case we need to round our base up so that we do not share a base. + // We may then find that our rounded up base makes us degenerate and ... roll over. + // + // Note that this is a closed/open interval. The end offset is NOT a member of this + // range. Consider an 8KiB file divided 50:50 into two 4KB ranges. The first range is + // [0,4KB) and the second is [4KB, 8KB). The IO at offset 4KB belongs to the second + // range, not the first. + // + + // + // Skip holes. These have the effect of excluding a range of the target by way of + // zero IO will be issued to them; the resulting range is still IO 0-100%. + // + + if (!r._span) { + continue; + } + + UINT64 b, e; + + b = ((r._dst.first * _relTargetSizeAligned) / 100); + // guarantee end (don't lose it in integer math) + if (r._dst.first + r._dst.second == 100) + { + e = _relTargetSizeAligned; + } + else + { + e = b + ((r._dst.second * _relTargetSizeAligned) / 100); + } + + e = ROUND_DOWN(e, _target->GetBlockAlignmentInBytes()); + + // unaligned/unaligned-same + // carryover IO% to next/last range + if (e < b) + { + // is the prior range degenerate? + // if so, extend its IO% + // note that this cannot happen for the first range, so there + // will always be a range to look at. + if (_vDistributionRange.rbegin()->_dst.first == e) + { + _vDistributionRange.rbegin()->_span += r._span; + } + // carry over to next + else + { + ioCarry = r._span; + } + + continue; + } + + b = ROUND_DOWN(b, _target->GetBlockAlignmentInBytes()); + + // Now if b < e (a positive range) we may discover we're adjacent + // to a degenerate range. This is the case of re-aligning b up. + // Note that the degenerate range logically rounds up - this does + // not affect operation, but presents the correct appearance of a + // closed/open interval with respect to the subsequent range. + // Case: -rdpct10/1:10/1 + // + // It is possible b == e: this is a case where b was already aligned + // and we're placing a normal degenerate span. No special handling. + + if (b < e && + _vDistributionRange.size() && + _vDistributionRange.rbegin()->_dst.first == b) + { + + b += _target->GetBlockAlignmentInBytes(); + _vDistributionRange.rbegin()->_dst.second += _target->GetBlockAlignmentInBytes(); + + // Now there are two degenerate cases to manage. + + // if we're dealing with a degenerate at the tail, allow carryover + if (b == _relTargetSizeAligned) + { + ioCarry = r._span; + continue; + } + + // otherwise, if the range became degenerate in the up-alignment, it must + // combine with the prior degenerate since its logical range is included + // with it. + if (b == e) + { + _vDistributionRange.rbegin()->_span += r._span; + continue; + } + + // fall through to place re-aligned b/e (non degenerate) + } + + // prefer to roll IO% to the smaller of prior range/this range + if (ioCarry && + _vDistributionRange.rbegin()->_span < r._span) + { + _vDistributionRange.rbegin()->_span += ioCarry; + ioCarry = 0; + } + + _vDistributionRange.emplace_back( + r._src - ioCarry, + r._span + ioCarry, + make_pair(b, e - b)); + + ioCarry = 0; + } + + // Apply trailing carryover to final range, extending it. + // Guarantee target range extends to aligned size - rollover is always from + // a degenerate range we could not place directly. We need to gross up the + // actual tail so that the effective correctly spans the open/closed interval + // to target size. + // -rdpct10/96:10/3:80/1 - the last range is degenerate and needs to roll. + if (ioCarry) + { + DistributionRange& last = *_vDistributionRange.rbegin(); + + last._span += ioCarry; + last._dst.second = _relTargetSizeAligned - last._dst.first; + } + } + break; + + case DistributionType::Absolute: + { + UINT32 ioUsed = 0; + + for (auto& r : _target->GetDistributionRange()) + { + // + // The premise for absolute distributions is similar but without the complication of + // degenerate ranges. The offsets are provided and we only need to push the last to + // the end of the range if it was left open (its length is zero). They do not need to + // be aligned, similar to -T thread stride - this is the caller's dilemma. We already + // know by validation that IO can be issued in the range since any absolute distribution + // with a range < block size would have been rejected. + // + // If the range was not left open we have two cases: + // + // * the end is within the final range + // * the end is past it + // + // If the end is within the final range that will again be the caller's dilemma, we'll + // simply trim the length of that range. If it is past it, we will discard the trailing + // ranges and trim the maximum IO% so that they become a proportional specification of the + // IO. For instance, if a 10/10/80 winds up with the 80% not addressable in the file, the + // maximum IO% trims to 20 and it logically becomes a 50:50 split (10:10). + // + + UINT64 l; + + // + // Skip holes. These have the effect of excluding a range of the target by way of + // zero IO will be issued to them; the resulting range is still IO 0-100%. + // + + if (!r._span) { + continue; + } + + // beyond end? done, with whatever tail IO% not seen + if (r._dst.first >= _relTargetSize) + { + break; + } + // open end or spans end? - set to aligned remainder + else if (r._dst.second == 0 || + r._dst.first + r._dst.second > _relTargetSize) + { + // ensure tail can accept IO by blocksize - caller has stated this is aligned by + // its specification + l = _relTargetSize - r._dst.first; + + if (l < _target->GetBlockSizeInBytes()) + { + break; + } + } + else + { + l = r._dst.second; + } + + _vDistributionRange.emplace_back( + r._src, + r._span, + make_pair(r._dst.first, l)); + + ioUsed += r._span; + } + + // reduce the IO distribution to that specified by the ranges consumed. + // it is still logically 100%, simply over a range of less than 0-100. + _ioDistributionSpan = ioUsed; + } + break; + + // none + default: + break; + } + + Reset(); + } + + // + // Reset IO pointer/type state to initial conditions. + // + + VOID Reset() + { + // + // Now set the (base-relative) initial sequential offset + // * sequential: based on thread stride + // * mixed: randomized starting position + // + // Note this is repeated for ParallelAsync initialization since sequential offset is in the IO request there. + // + + switch (_mode) + { + case IOMode::Sequential: + _nextSeqOffset = _target->GetThreadBaseRelativeOffsetInBytes(_tp->ulRelativeThreadNo); + break; + + case IOMode::Mixed: + _nextSeqOffset = NextRelativeRandomOffset(); + break; + + default: + break; + } + + _lastIO = NextIOType(true); + } + + // + // Validate whether this thread can start IO given thread stride and file size. + // + + bool CanStart() + { + UINT64 startingFileOffset = _target->GetThreadBaseRelativeOffsetInBytes(_tp->ulRelativeThreadNo); + + if (startingFileOffset + _target->GetBlockSizeInBytes() > _relTargetSize) + { + return false; + } + + return true; + } + + UINT64 TargetSize() + { + return _targetSize; + } + + VOID InitializeParallelAsyncIORequest(IORequest& ioRequest) const + { + ULARGE_INTEGER initialOffset; + + // + // Bias backwards by one IO so that this functions as the last-IO-issued pointer. + // It will be incremented to the expected first offset. Note: absolute offset. + // + + initialOffset.QuadPart = _target->GetThreadBaseFileOffsetInBytes(_tp->ulRelativeThreadNo) - _target->GetBlockAlignmentInBytes(); + + ioRequest.GetOverlapped()->Offset = initialOffset.LowPart; + ioRequest.GetOverlapped()->OffsetHigh = initialOffset.HighPart; + } + + UINT64 NextRelativeSeqOffset() + { + UINT64 nextOffset; + + nextOffset = _nextSeqOffset; + + // Wrap? + + if (nextOffset + _target->GetBlockSizeInBytes() > _relTargetSize) { + nextOffset = _target->GetThreadBaseRelativeOffsetInBytes(_tp->ulRelativeThreadNo) % _target->GetBlockAlignmentInBytes(); + } + + _nextSeqOffset = nextOffset + _target->GetBlockAlignmentInBytes(); + + return nextOffset; + } + + UINT64 NextRelativeInterlockedSeqOffset() + { + UINT64 nextOffset; + + // advance shared and rewind to get offset to use + nextOffset = InterlockedAdd64((PLONG64) _sharedSeqOffset, _target->GetBlockAlignmentInBytes()); + nextOffset -= _target->GetBlockAlignmentInBytes(); + + nextOffset %= _relTargetSizeAligned; + return nextOffset; + } + + UINT64 NextRelativeParaSeqOffset(IORequest& ioRequest) + { + ULARGE_INTEGER nextOffset; + + // + // Note: parallel seq differs from the other sequential cases in that the + // pointer indicates the prior IO, not the offset to issue the current at. + // Advance it. + // + + nextOffset.LowPart = ioRequest.GetOverlapped()->Offset; + nextOffset.HighPart = ioRequest.GetOverlapped()->OffsetHigh; + nextOffset.QuadPart -= _target->GetBaseFileOffsetInBytes(); // absolute -> relative + nextOffset.QuadPart += _target->GetBlockAlignmentInBytes(); // advance past last IO (!) + + // Wrap? + + if (nextOffset.QuadPart + _target->GetBlockSizeInBytes() > _relTargetSize) { + nextOffset.QuadPart = _target->GetThreadBaseRelativeOffsetInBytes(_tp->ulRelativeThreadNo) % _target->GetBlockAlignmentInBytes(); + } + + return nextOffset.QuadPart; + } + + UINT64 NextRelativeRandomOffset() const + { + UINT64 nextOffset = _tp->pRand->Rand64(); + nextOffset -= nextOffset % _target->GetBlockAlignmentInBytes(); + + // + // With a distribution we choose by bucket. Note the bucket is already aligned. + // + + if (_vDistributionRange.size()) + { + auto r = DistributionRange::find(_vDistributionRange, _tp->pRand->Rand64() % _ioDistributionSpan); + nextOffset %= r->_dst.second; // trim to range length (already aligned) + nextOffset += r->_dst.first; // bump by range base + } + // Full width. + else + { + nextOffset %= _relTargetSizeAligned; + } + + return nextOffset; + } + + UINT64 NextRelativeMixedOffset(bool& fRandom) + { + ULARGE_INTEGER nextOffset; + + fRandom = Util::BooleanRatio(_tp->pRand, _target->GetRandomRatio()); + + if (fRandom) + { + nextOffset.QuadPart = NextRelativeRandomOffset(); + _nextSeqOffset = nextOffset.QuadPart + _target->GetBlockAlignmentInBytes(); + return nextOffset.QuadPart; + } + + return NextRelativeSeqOffset(); + } + + IOOperation NextIOType(bool newType) + { + IOOperation ioType; + + if (_target->GetWriteRatio() == 0) + { + ioType = IOOperation::ReadIO; + } + else if (_target->GetWriteRatio() == 100) + { + ioType = IOOperation::WriteIO; + } + else if (_mode == IOMode::Mixed && !newType) + { + // repeat last IO if not needing a new choice (e.g., random) + ioType = _lastIO; + } + else + { + ioType = Util::BooleanRatio(_tp->pRand, _target->GetWriteRatio()) ? IOOperation::WriteIO : IOOperation::ReadIO; + _lastIO = ioType; + } + + return ioType; + } + + void NextIORequest(IORequest &ioRequest) + { + bool fRandom = false; + ULARGE_INTEGER nextOffset = { 0 }; + + switch (_mode) + { + case IOMode::Sequential: + nextOffset.QuadPart = NextRelativeSeqOffset(); + break; + + case IOMode::InterlockedSequential: + nextOffset.QuadPart = NextRelativeInterlockedSeqOffset(); + break; + + case IOMode::ParallelAsync: + nextOffset.QuadPart = NextRelativeParaSeqOffset(ioRequest); + break; + + case IOMode::Mixed: + nextOffset.QuadPart = NextRelativeMixedOffset(fRandom); + break; + + case IOMode::Random: + nextOffset.QuadPart = NextRelativeRandomOffset(); + fRandom = true; + break; + + default: + assert(false); + } + + // + // Convert relative offset to absolute. + // + + nextOffset.QuadPart += _target->GetBaseFileOffsetInBytes(); + + // + // Move offset into the IO request and decide what IO type will be issued. + // Mixed which has chosen sequential will repeat last IO type so that seq + // runs are homogeneous. + // + + ioRequest.GetOverlapped()->Offset = nextOffset.LowPart; + ioRequest.GetOverlapped()->OffsetHigh = nextOffset.HighPart; + ioRequest.SetIoType(NextIOType(fRandom)); + } + + private: + + const ThreadParameters *_tp; + const Target *_target; + const UINT64 _targetSize; // unmodified absolute target size + const IOMode _mode; // thread's mode of IO operations to this target (Random, Sequential, etc.) + + // + // Offsets/sizes are zero-based relative to target base offset, not absolute file offset. + // Relative size is trimmed with respect to block alignment, if specified. + // + + UINT64 _relTargetSize; // relative target size for IO v. base/max + UINT64 _relTargetSizeAligned; // relative target size for zero-base aligned IO (applies to: Random, InterlockedSequential) + UINT64 _nextSeqOffset; // next IO offset to issue sequential IO at (applies to: Sequential & Mixed) + volatile UINT64 *_sharedSeqOffset; // ... for interlocked IO (applies to: InterlockedSequential) + IOOperation _lastIO; // last IO type (applies to: Mixed) + +public: + + // + // Random distribution (stated in absolute offsets of target) + // + + vector _vDistributionRange; + UINT32 _ioDistributionSpan; + + friend class UnitTests::IORequestGeneratorUnitTests; +}; + +class IResultParser +{ +public: + virtual string ParseResults(const Profile& profile, const SystemInformation& system, vector vResults) = 0; + virtual string ParseProfile(const Profile& profile) = 0; + /// for CrystalDiskMark + virtual int GetTotalScore() = 0; + virtual double GetAverageLatency() = 0; +}; + +class EtwResultParser +{ +public: + static void ParseResults(vector vResults); + +private: + static void _WriteResults(IOOperation type, const TargetResults& targetResults, size_t uThread); +}; diff --git a/CristalDiskMark/source/diskspd22/Common/Histogram.h b/CristalDiskMark/source/diskspd22/Common/Histogram.h new file mode 100644 index 0000000..abe5d2b --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Common/Histogram.h @@ -0,0 +1,330 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +// Plain min/max macros from common headers will interfere with std::numeric_limits + +#pragma push_macro("min") +#pragma push_macro("max") +#undef min +#undef max + +template +class Histogram +{ +private: + + mutable std::unordered_map _data; + mutable unsigned _samples; + + mutable std::map _sdata; + mutable unsigned _ssamples; + + // Save most recent percentile/iterator/nth-distance query. If the next is strictly >= it allows + // an efficient forward iteration through an ascending set of queries. + + mutable double _lastptile; + mutable unsigned _lastptilen; + mutable decltype(_sdata.cbegin()) _lastptilepos; + + // A histogram starts writable/unsealed and automatically seals after the first read operation. + // Subsequent writes which add data restart from empty. + + void _SealData() const + { + if (!_data.empty()) + { + _sdata.clear(); + + _sdata = std::map(_data.cbegin(), _data.cend()); + _ssamples = _samples; + + // invalid ptile > 1; first ptile query will initialize + _lastptile = 1.1; + + _data.clear(); + _samples = 0; + } + } + +public: + + Histogram() + : _samples(0), + _ssamples(0) + {} + + void Clear() + { + _data.clear(); + _samples = 0; + + _sdata.clear(); + _ssamples = 0; + + } + + void Add(T v) + { + _data[v]++; + _samples++; + } + + void Merge(const Histogram &other) + { + for (auto i : other._data) + { + _data[ i.first ] += i.second; + } + + _samples += other._samples; + } + + T GetMin() const + { + _SealData(); + + // Default low if empty + if (!_ssamples) + { + return std::numeric_limits::min(); + } + + return _sdata.cbegin()->first; + } + + T GetMax() const + { + _SealData(); + + // Default low if empty + if (!_ssamples) + { + return std::numeric_limits::min(); + } + + return _sdata.crbegin()->first; + } + + unsigned GetSampleBuckets() const + { + return (unsigned) (_samples ? _data.size() : _sdata.size()); + } + + unsigned GetSampleSize() const + { + return _samples ? _samples : _ssamples; + } + + T GetPercentile(double p) const + { + if ((p < 0.0) || (p > 1.0)) + { + throw std::invalid_argument("Percentile must be >= 0 and <= 1"); + } + + _SealData(); + + // Default low if empty + if (!_ssamples) + { + return std::numeric_limits::min(); + } + + const double target = p * _ssamples; + + // Default to beginning; n is the number of samples iterated over so far + unsigned n = 0; + auto pos = _sdata.cbegin(); + + // Resume from last? + if (p >= _lastptile) + { + n = _lastptilen; + pos = _lastptilepos; + } + + while (pos != _sdata.cend()) + { + if (n + pos->second >= target) + { + // Save position. Note the pre-incremented distance through the histogram + // must be saved in case next is still in the same bucket. + _lastptile = p; + _lastptilen = n; + _lastptilepos = pos; + + return pos->first; + } + + n += pos->second; + ++pos; + } + + throw std::overflow_error("overran end trying to find percentile"); + } + + T GetPercentile(int p) const + { + return GetPercentile(static_cast(p) / 100); + } + + T GetMedian() const + { + return GetPercentile(0.5); + } + + double GetStdDev() const { return GetStandardDeviation(); } + double GetAvg() const { return GetMean(); } + + double GetMean() const + { + _SealData(); + + // Default low if empty + if (!_ssamples) + { + return std::numeric_limits::min(); + } + + double sum(0); + + for (const auto i : _sdata) + { + double bucket_val = + static_cast(i.first) * i.second / _ssamples; + + if (sum + bucket_val < 0) + { + throw std::overflow_error("while trying to accumulate sum"); + } + + sum += bucket_val; + } + + return sum; + } + + double GetStandardDeviation() const + { + double mean(GetMean()); + double ssd(0); + + for (const auto i : _sdata) + { + double dev = static_cast(i.first) - mean; + double sqdev = dev * dev; + ssd += i.second * sqdev; + } + + return sqrt(ssd / _ssamples); + } + + std::string GetHistogramCsv(const unsigned bins) const + { + return GetHistogramCsv(bins, GetMin(), GetMax()); + } + + std::string GetHistogramCsv(const unsigned bins, const T LOW, const T HIGH) const + { + _SealData(); + + // ISSUE-REVIEW + // Currently bins are defined as strictly less-than + // their upper limit, with the exception of the last + // bin. Otherwise where would I put the max value? + const double binSize = static_cast((HIGH - LOW) / bins); + double limit = static_cast(LOW); + + std::ostringstream os; + os.precision(std::numeric_limits::digits10); + + auto pos = _sdata.cbegin(); + unsigned cumulative = 0; + + for (unsigned bin = 1; bin <= bins; ++bin) + { + unsigned count = 0; + limit += binSize; + + while (pos != _sdata.end() && + (pos->first < limit || bin == bins)) + { + count += pos->second; + ++pos; + } + + cumulative += count; + + os << limit << "," << count << "," << cumulative << std::endl; + } + + return os.str(); + } + + std::string GetRawCsv() const + { + _SealData(); + + std::ostringstream os; + os.precision(std::numeric_limits::digits10); + + for (const auto i : _sdata) + { + os << i.first << "," << i.second << std::endl; + } + + return os.str(); + } + + std::string GetRaw() const + { + _SealData(); + + std::ostringstream os; + + for (const auto i : _sdata) + { + os << i.second << " " << i.first << std::endl; + } + + return os.str(); + } +}; + +#pragma pop_macro("min") +#pragma pop_macro("max") diff --git a/CristalDiskMark/source/diskspd22/Common/IORequestGenerator.h b/CristalDiskMark/source/diskspd22/Common/IORequestGenerator.h new file mode 100644 index 0000000..550a579 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Common/IORequestGenerator.h @@ -0,0 +1,84 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#define INITGUID //Include this #define to use SystemTraceControlGuid in Evntrace.h. +#include //ETW +#include //ntdll.dll + + +void PrintError(const char *format, ...); +namespace UnitTests +{ + class IORequestGeneratorUnitTests; +} + +class IORequestGenerator +{ +public: + IORequestGenerator() : + _hNTDLL(nullptr) + { + + } + + //bool GenerateRequests(Profile& profile, IResultParser& resultParser, struct Synchronization *pSynch); + /// for CrystalDiskMark + bool GenerateRequests(Profile& profile, IResultParser& resultParser, struct Synchronization* pSynch, int* totalScore, double* averageLatency); + +private: + + struct CreateFileParameters + { + string sPath; + UINT64 ullFileSize; + bool fZeroWriteBuffers; + }; + + bool _GenerateRequestsForTimeSpan(const Profile& profile, const TimeSpan& timeSpan, Results& results, struct Synchronization *pSynch); + void _AbortWorkerThreads(HANDLE hStartEvent, vector& vhThreads) const; + void _CloseOpenFiles(vector& vhFiles) const; + DWORD _CreateDirectoryPath(const char *path) const; + bool _CreateFile(UINT64 ullFileSize, const char *pszFilename, bool fZeroBuffers, bool fVerbose) const; + bool _GetActiveGroupsAndProcs() const; + struct ETWSessionInfo _GetResultETWSession(const EVENT_TRACE_PROPERTIES *pTraceProperties) const; + bool _GetSystemPerfInfo(vector& vSPPI, bool fVerbose) const; + void _InitializeGlobalParameters(); + bool _LoadDLLs(); + bool _StopETW(bool fUseETW, TRACEHANDLE hTraceSession) const; + void _TerminateWorkerThreads(vector& vhThreads) const; + bool _ValidateProfile(const Profile& profile) const; + vector _GetFilesToPrecreate(const Profile& profile) const; + void _MarkFilesAsCreated(Profile& profile, const vector& vFiles) const; + bool _PrecreateFiles(Profile& profile) const; + + HINSTANCE volatile _hNTDLL; //handle to ntdll.dll + + friend class UnitTests::IORequestGeneratorUnitTests; +}; diff --git a/CristalDiskMark/source/diskspd22/Common/IoBucketizer.cpp b/CristalDiskMark/source/diskspd22/Common/IoBucketizer.cpp new file mode 100644 index 0000000..672eb54 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Common/IoBucketizer.cpp @@ -0,0 +1,228 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "IoBucketizer.h" +#include + +/* +Calculating stddev using an online algorithm: +avg = sum(1..n, a[n]) / n +stddev = sqrt(sum(1..n, (a[n] - avg)^2) / n) + = sqrt(sum(1..n, a[n]^2 - 2 * a[n] * avg + avg^2) / n) + = sqrt((sum(1..n, a[n]^2) - 2 * avg * sum(1..n, a[n]) + n * avg^2) / n) + = sqrt((sum(1..n, a[n]^2) - 2 * (sum(1..n, a[n]) / n) * sum(1..n, a[n]) + n * (sum(1..n], a[n]) / n)^2) / n) + = sqrt((sum(1..n, a[n]^2) - (2 / n) * sum(1..n, a[n])^2 + (1 / n) * sum(1..n, a[n])^2) / n) + = sqrt((sum(1..n, a[n]^2) - (1 / n) * sum(1..n, a[n])^2) / n) + +So if we track n, sum(a[n]) and sum(a[n]^2) we can calculate the stddev. This +is used to calculate the stddev of the latencies below. +*/ + +const unsigned __int64 INVALID_BUCKET_DURATION = 0; + +IoBucketizer::IoBucketizer() + : _bucketDuration(INVALID_BUCKET_DURATION), + _validBuckets(0), + _totalBuckets(0) +{} + +void IoBucketizer::Initialize(unsigned __int64 bucketDuration, size_t validBuckets) +{ + if (_bucketDuration != INVALID_BUCKET_DURATION) + { + throw std::runtime_error("IoBucketizer has already been initialized"); + } + if (bucketDuration == INVALID_BUCKET_DURATION) + { + throw std::invalid_argument("Bucket duration must be a positive integer"); + } + + _bucketDuration = bucketDuration; + _validBuckets = validBuckets; + _vBuckets.resize(_validBuckets); +} + +void IoBucketizer::Add(unsigned __int64 ioCompletionTime, double ioDuration) +{ + if (_bucketDuration == INVALID_BUCKET_DURATION) + { + throw std::runtime_error("IoBucketizer has not been initialized"); + } + + size_t bucketNumber = static_cast(ioCompletionTime / _bucketDuration); + _totalBuckets = bucketNumber + 1; + + if (bucketNumber >= _validBuckets) + { + return; + } + + _vBuckets[bucketNumber].lfSumDuration += ioDuration; + _vBuckets[bucketNumber].lfSumSqrDuration += ioDuration * ioDuration; + + if (_vBuckets[bucketNumber].ulCount == 0 || + ioDuration < _vBuckets[bucketNumber].lfMinDuration) + { + _vBuckets[bucketNumber].lfMinDuration = ioDuration; + } + if (_vBuckets[bucketNumber].ulCount == 0 || + ioDuration > _vBuckets[bucketNumber].lfMaxDuration) + { + _vBuckets[bucketNumber].lfMaxDuration = ioDuration; + } + + _vBuckets[bucketNumber].ulCount++; +} + +size_t IoBucketizer::GetNumberOfValidBuckets() const +{ + return (_totalBuckets > _validBuckets ? _validBuckets : _totalBuckets); +} + +unsigned int IoBucketizer::GetIoBucketCount(size_t bucketNumber) const +{ + if (bucketNumber < _validBuckets) + { + return _vBuckets[bucketNumber].ulCount; + } + + return 0; +} + +double IoBucketizer::GetIoBucketMinDurationUsec(size_t bucketNumber) const +{ + if (bucketNumber < _validBuckets) + { + return _vBuckets[bucketNumber].lfMinDuration; + } + + return 0; +} + +double IoBucketizer::GetIoBucketMaxDurationUsec(size_t bucketNumber) const +{ + if (bucketNumber < _validBuckets) + { + return _vBuckets[bucketNumber].lfMaxDuration; + } + + return 0; +} + +double IoBucketizer::GetIoBucketAvgDurationUsec(size_t bucketNumber) const +{ + if (bucketNumber < _validBuckets && _vBuckets[bucketNumber].ulCount != 0) + { + return _vBuckets[bucketNumber].lfSumDuration / static_cast(_vBuckets[bucketNumber].ulCount); + } + + return 0; +} + +double IoBucketizer::GetIoBucketDurationStdDevUsec(size_t bucketNumber) const +{ + if (bucketNumber < _validBuckets && _vBuckets[bucketNumber].ulCount != 0) + { + double sum_of_squares = _vBuckets[bucketNumber].lfSumSqrDuration; + double square_of_sum = _vBuckets[bucketNumber].lfSumDuration * _vBuckets[bucketNumber].lfSumDuration; + double count = static_cast(_vBuckets[bucketNumber].ulCount); + double square_stddev = (sum_of_squares - (square_of_sum / count)) / count; + + return sqrt(square_stddev); + } + + return 0; +} + +double IoBucketizer::_GetMeanIOPS() const +{ + size_t numBuckets = GetNumberOfValidBuckets(); + double sum = 0; + + for (size_t i = 0; i < numBuckets; i++) + { + sum += static_cast(_vBuckets[i].ulCount) / numBuckets; + } + + return sum; +} + +double IoBucketizer::GetStandardDeviationIOPS() const +{ + size_t numBuckets = GetNumberOfValidBuckets(); + + if(numBuckets == 0) + { + return 0.0; + } + + double mean = _GetMeanIOPS(); + double ssd = 0; + + for (size_t i = 0; i < numBuckets; i++) + { + double dev = static_cast(_vBuckets[i].ulCount) - mean; + double sqdev = dev*dev; + ssd += sqdev; + } + + return sqrt(ssd / numBuckets); +} + +void IoBucketizer::Merge(const IoBucketizer& other) +{ + if(other._vBuckets.size() > _vBuckets.size()) + { + _vBuckets.resize(other._vBuckets.size()); + } + for(size_t i = 0; i < other._vBuckets.size(); i++) + { + _vBuckets[i].ulCount += other._vBuckets[i].ulCount; + _vBuckets[i].lfSumDuration += other._vBuckets[i].lfSumDuration; + _vBuckets[i].lfSumSqrDuration += other._vBuckets[i].lfSumSqrDuration; + + if (i >= _validBuckets || + other._vBuckets[i].lfMinDuration < _vBuckets[i].lfMinDuration) + { + _vBuckets[i].lfMinDuration = other._vBuckets[i].lfMinDuration; + } + if (other._vBuckets[i].lfMaxDuration > _vBuckets[i].lfMaxDuration) + { + _vBuckets[i].lfMaxDuration = other._vBuckets[i].lfMaxDuration; + } + } + if (other._validBuckets > _validBuckets) + { + _validBuckets = other._validBuckets; + } + if (other._totalBuckets > _totalBuckets) + { + _totalBuckets = other._totalBuckets; + } +} diff --git a/CristalDiskMark/source/diskspd22/Common/IoBucketizer.h b/CristalDiskMark/source/diskspd22/Common/IoBucketizer.h new file mode 100644 index 0000000..6d501c4 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Common/IoBucketizer.h @@ -0,0 +1,73 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + +#include + +class IoBucketizer +{ +public: + IoBucketizer(); + void Initialize(unsigned __int64 bucketDuration, size_t validBuckets); + + size_t GetNumberOfValidBuckets() const; + unsigned int GetIoBucketCount(size_t bucketNumber) const; + double GetIoBucketMinDurationUsec(size_t bucketNumber) const; + double GetIoBucketMaxDurationUsec(size_t bucketNumber) const; + double GetIoBucketAvgDurationUsec(size_t bucketNumber) const; + double GetIoBucketDurationStdDevUsec(size_t bucketNumber) const; + void Add(unsigned __int64 ioCompletionTime, double ioDuration); + double GetStandardDeviationIOPS() const; + void Merge(const IoBucketizer& other); +private: + double _GetMeanIOPS() const; + + struct IoBucket { + IoBucket() : + ulCount(0), + lfMinDuration(0), + lfMaxDuration(0), + lfSumDuration(0), + lfSumSqrDuration(0) + { + } + + unsigned int ulCount; + double lfMinDuration; + double lfMaxDuration; + double lfSumDuration; + double lfSumSqrDuration; + }; + + unsigned __int64 _bucketDuration; + size_t _validBuckets; + size_t _totalBuckets; + std::vector _vBuckets; +}; diff --git a/CristalDiskMark/source/diskspd22/Common/OverlappedQueue.h b/CristalDiskMark/source/diskspd22/Common/OverlappedQueue.h new file mode 100644 index 0000000..622ac67 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Common/OverlappedQueue.h @@ -0,0 +1,50 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#include + +// +// OverlappedQueue is a simple class that implements a queue for OVERLAPPED elements +// +class OverlappedQueue +{ +public: + OverlappedQueue(void); + + void Add(OVERLAPPED *pOverlapped); + bool IsEmpty(void) const; + OVERLAPPED * Remove(void); + size_t GetCount() const; + +private: + OVERLAPPED *_pHead; + OVERLAPPED *_pTail; + size_t _cItems; +}; \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Common/ResultParser.h b/CristalDiskMark/source/diskspd22/Common/ResultParser.h new file mode 100644 index 0000000..3015fab --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Common/ResultParser.h @@ -0,0 +1,76 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#include "Common.h" + +namespace UnitTests +{ + class ResultParserUnitTests; +} + +class ResultParser : public IResultParser +{ +public: + string ParseResults(const Profile& profile, const SystemInformation& system, vector vResults); + string ParseProfile(const Profile& profile); + + /// for CrystalDiskMark + int GetTotalScore(); + double GetAverageLatency(); + +private: + void _DisplayFileSize(UINT64 fsize, UINT32 align = 0); + void _DisplayETWSessionInfo(struct ETWSessionInfo sessionInfo); + void _DisplayETW(struct ETWMask ETWMask, struct ETWEventCounters EtwEventCounters); + void _Print(const char *format, ...); + void _PrintProfile(const Profile& profile); + void _PrintSystemInfo(const SystemInformation& system); + void _PrintCpuUtilization(const Results& results, const SystemInformation& system); + enum class _SectionEnum {TOTAL, READ, WRITE}; + void _PrintSectionFieldNames(const TimeSpan& timeSpan); + void _PrintSectionBorderLine(const TimeSpan& timeSpan); + void _PrintSection(_SectionEnum, const TimeSpan&, const Results&); + void _PrintLatencyPercentiles(const Results&); + void _PrintLatencyChart(const Histogram& readLatencyHistogram, + const Histogram& writeLatencyHistogram, + const Histogram& totalLatencyHistogram); + void _PrintTimeSpan(const TimeSpan &timeSpan); + void _PrintTarget(const Target &target, bool fUseThreadsPerFile, bool fUseRequestsPerFile, bool fCompletionRoutines); + void _PrintDistribution(DistributionType dT, const vector& v, char* spc); + void _PrintEffectiveDistributions(const Results& results); + void _PrintWaitStats(const Results& result); + + string _sResult; + /// for CrystalDiskMark + int _totalScore; + double _averageLatency; + + friend class UnitTests::ResultParserUnitTests; +}; \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Common/ThroughputMeter.h b/CristalDiskMark/source/diskspd22/Common/ThroughputMeter.h new file mode 100644 index 0000000..0375f05 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Common/ThroughputMeter.h @@ -0,0 +1,61 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#include + +// ThroughputMeter class assists in metering out throughput over +// time. The meter is started by calling Start() with the throughput +// to be simulated. GetSleepTime() returns 0 when the next IO can be issued. +// Adjust() is called to notify the ThroughputMeter about how many bytes were read/written. +class ThroughputMeter +{ +public: + ThroughputMeter(void); + + bool IsRunning(void) const; + void Start(DWORD cBytesPerMillisecond, DWORD dwBlockSize, DWORD dwThinkTime, DWORD dwBurstSize); + DWORD GetSleepTime(void) const; + void Adjust(size_t cb); + +private: + DWORD _GetThrottleTime(void) const; + + bool _fRunning; // true = throughput monitoring is on + bool _fThrottle; // true = throttling is on + bool _fThink; // true = think time is enabled + ULONGLONG _cbCompleted; // completed IO + DWORD _cbBlockSize; + DWORD _cBytesPerMillisecond; // rate of throttling + ULONGLONG _ullStartTimestamp; + ULONGLONG _ullDelayUntil; // timestamp at which the next IO can be executed + DWORD _thinkTime; // time to sleep between burst of IOs + DWORD _burstSize; // number of IOs in a burst. meaningless if think time is zero + DWORD _cIO; // count of IOs in the current burst +}; \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Common/Version.h b/CristalDiskMark/source/diskspd22/Common/Version.h new file mode 100644 index 0000000..a04a6e9 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Common/Version.h @@ -0,0 +1,52 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +// versioning material. for simplicity in consumption, please ensure that the date string +// parses via the System.Datetime constructor as follows (in Powershell): +// +// [datetime] "string" +// +// this should result in a valid System.Datetime object, rendered like: +// +// Monday, June 16, 2014 12:00:00 AM + +#define DISKSPD_RELEASE_TAG "" +#define DISKSPD_REVISION "" + +#define DISKSPD_MAJOR 2 +#define DISKSPD_MINOR 2 +#define DISKSPD_BUILD 0 +#define DISKSPD_QFE 0 + +#define DISKSPD_MAJORMINOR_VER_STR(x,y,z) #x "." #y "." #z +#define DISKSPD_MAJORMINOR_VERSION_STRING(x,y,z) DISKSPD_MAJORMINOR_VER_STR(x,y,z) +#define DISKSPD_MAJORMINOR_VERSION_STR DISKSPD_MAJORMINOR_VERSION_STRING(DISKSPD_MAJOR, DISKSPD_MINOR, DISKSPD_BUILD) + +#define DISKSPD_NUMERIC_VERSION_STRING DISKSPD_MAJORMINOR_VERSION_STR DISKSPD_REVISION DISKSPD_RELEASE_TAG +#define DISKSPD_DATE_VERSION_STRING "2024/6/3" diff --git a/CristalDiskMark/source/diskspd22/Common/XmlProfileParser.h b/CristalDiskMark/source/diskspd22/Common/XmlProfileParser.h new file mode 100644 index 0000000..0d1ab3e --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Common/XmlProfileParser.h @@ -0,0 +1,65 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#include +#include "Common.h" + +class XmlProfileParser +{ +public: + bool ParseFile(const char *pszPath, Profile *pProfile, vector *pvSubstTargets, HMODULE hModule); + +private: + HRESULT _ParseEtw(IXMLDOMDocument2 *pXmlDoc, Profile *pProfile); + HRESULT _ParseTimeSpans(IXMLDOMDocument2 *pXmlDoc, Profile *pProfile, vector>& vSubsts); + HRESULT _ParseTimeSpan(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan, vector>& vSubsts); + HRESULT _ParseTargets(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan, vector>& vSubsts); + HRESULT _ParseRandomDataSource(IXMLDOMNode *pXmlNode, Target *pTarget); + HRESULT _ParseWriteBufferContent(IXMLDOMNode *pXmlNode, Target *pTarget); + HRESULT _ParseTarget(IXMLDOMNode *pXmlNode, Target *pTarget); + HRESULT _ParseThreadTargets(IXMLDOMNode *pXmlNode, Target *pTarget); + HRESULT _ParseThroughput(IXMLDOMNode *pXmlNode, Target *pTarget); + HRESULT _ParseThreadTarget(IXMLDOMNode *pXmlNode, ThreadTarget *pThreadTarget); + HRESULT _ParseAffinityAssignment(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan); + HRESULT _ParseAffinityGroupAssignment(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan); + HRESULT _ParseDistribution(IXMLDOMNode *pXmlNode, Target *pTarget); + HRESULT _SubstTarget(Target *pTarget, vector>& vSubsts); + + HRESULT _GetString(IXMLDOMNode *pXmlNode, const char *pszQuery, string *psValue) const; + HRESULT _GetUINT32(IXMLDOMNode *pXmlNode, const char *pszQuery, UINT32 *pulValue) const; + HRESULT _GetUINT64(IXMLDOMNode *pXmlNode, const char *pszQuery, UINT64 *pullValue) const; + HRESULT _GetDWORD(IXMLDOMNode *pXmlNode, const char *pszQuery, DWORD *pdwValue) const; + HRESULT _GetBool(IXMLDOMNode *pXmlNode, const char *pszQuery, bool *pfValue) const; + + HRESULT _GetUINT32Attr(IXMLDOMNode *pXmlNode, const char *pszAttr, UINT32 *pulValue) const; + + HRESULT _GetVerbose(IXMLDOMDocument2 *pXmlDoc, bool *pfVerbose); + HRESULT _GetProgress(IXMLDOMDocument2 *pXmlDoc, DWORD *pdwProgress); +}; \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Common/XmlResultParser.h b/CristalDiskMark/source/diskspd22/Common/XmlResultParser.h new file mode 100644 index 0000000..89c2678 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Common/XmlResultParser.h @@ -0,0 +1,61 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#include "Common.h" + +class XmlResultParser: public IResultParser +{ +public: + string ParseResults(const Profile& profile, const SystemInformation& system, vector vResults); + string ParseProfile(const Profile& profile); + /// for CrystalDiskMark + int GetTotalScore(); + double GetAverageLatency(); + +private: + void _PrintCpuUtilization(const Results& results, const SystemInformation& system); + void _PrintETW(struct ETWMask ETWMask, struct ETWEventCounters EtwEventCounters); + void _PrintETWSessionInfo(struct ETWSessionInfo sessionInfo); + void _PrintLatencyPercentiles(const Results& results); + void _PrintTargetResults(const TargetResults& results); + void _PrintTargetLatency(const TargetResults& results); + void _PrintTargetIops(const IoBucketizer& readBucketizer, const IoBucketizer& writeBucketizer, UINT32 bucketTimeInMs); + void _PrintOverallIops(const Results& results, UINT32 bucketTimeInMs); + void _PrintIops(const IoBucketizer& readBucketizer, const IoBucketizer& writeBucketizer, UINT32 bucketTimeInMs); + void _PrintWaitStats(const ThreadResults& threadResult); + + void _PrintV(const char *format, va_list listArg); + void _Print(const char *format, ...); + void _PrintInc(const char *format, ...); + void _PrintDec(const char *format, ...); + + string _sResult; + UINT32 _indent = 0; +}; \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Common/errors.h b/CristalDiskMark/source/diskspd22/Common/errors.h new file mode 100644 index 0000000..f11b5ca --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Common/errors.h @@ -0,0 +1,38 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#ifndef __DISKSPD_ERRORS_H +#define __DISKSPD_ERRORS_H + +#define ERROR_LOAD_LIBRARY 1 //error during LoadLibrary call +#define ERROR_GET_PROC_ADDRESS 2 //error during GetProcAddress +#define ERROR_PARSE_CMD_LINE 3 //error parsing cmd line parameters +#define ERROR_WAIT_FOR_START_SIGNAL 4 //error waiting for a signal to start work + +#endif \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Common/etw.h b/CristalDiskMark/source/diskspd22/Common/etw.h new file mode 100644 index 0000000..a14b670 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Common/etw.h @@ -0,0 +1,41 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + + +#include +#include ///WNODE_HEADER +#define INITGUID //Include this #define to use SystemTraceControlGuid in Evntrace.h. +#include //ETW +#include "Common.h" + +BOOL TraceEvents(); +TRACEHANDLE StartETWSession(const Profile& profile); +PEVENT_TRACE_PROPERTIES StopETWSession(TRACEHANDLE hTraceSession); \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/ArcVM-Setup.md b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/ArcVM-Setup.md new file mode 100644 index 0000000..d64535a --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/ArcVM-Setup.md @@ -0,0 +1,329 @@ +# From Setup to Start-FleetSweep for Arc Enabled Virtual Machines in HCI clusters + +This is the traditional path of setting up VMFleet to deploy Arc enabled VMs on HCI clusters and running it using your desired DiskSpd parameters/flags. + + + +## Prerequisites + +Before we begin setting up VMFleet, there are a few prerequisites that you should have ready. + +Ensure an HCI cluster is setup. + +Ensure that you have 1 CSV per node + +Within Storage Spaces Direct, CPU usage is based on the host. Therefore, it is recommended that you split the storage load by creating as many CSVs as there are host nodes. We can go ahead and create a CSV per node in the cluster. + +You may run the following: + + +``` +Get-ClusterNode |% { + New-Volume -StoragePoolFriendlyName SU1_Pool* -FriendlyName $_ -FileSystem CSVFS_ReFS -Size +} + +``` + +Ensure that you create a “collect” volume. + +You may run the following: + +``` + +New-Volume -StoragePoolFriendlyName SU1_Pool* -FriendlyName collect -FileSystem CSVFS_ReFS -Size 200GB + +``` + +If you have ran VMFleet in the past, please ensure that prior VMFleet directories are completely removed from existing volumes. + +Retrieve or install a Server Core VHDX file. If you do not have one handy, we can create a new one by following the below instructions. + +Download WS2019 Server Core ISO from the public website. + +Open Hyper-V Manager. + +Click “New”, then “Virtual Machine”. + +Navigate through the prompts and pick a location to store your VM. + +Once your VM is created, boot up the VM and follow the instructions. This is where you will decide your VM password, or what we will later call, “adminpass”. + +This is important as we will use this later, so make sure you write this down. + +Log out of the VM, and navigate to where you stored your VM. You should find a “Virtual Hard Disks” folder. Inside, you should find your new Server Core VHDX file. + +Rename it to “Base1.vhdx”. + +Copy or move the file to the cluster environment that you want to run VMFleet in. + +Done! + +### Deployment + +1. First, we need to install the new PowerShell Module from the PowerShell Gallery and then load it into our terminal. We also need to disable cache as it is not supported currently for ArcVMs. Run the following: + +``` +Install-Module -Name “VMFleet” +Import-Module VMFleet +Set-ClusterStorageSpacesDirect -CacheState Disabled; + +``` + + +2. Sanity Check: + Run "Get-Module VMFleet" to confirm the module exists. + + Run "Get-Command -Module VMFleet" to obtain a list of functions included in the module. + + We will now set up the directory structure within the “Collect” CSV created earlier. Run "Install-Fleet" + + This creates the necessary VMFleet directories which include: + + * Collect/control + + Contains arc.json which stores Arc configuration and the scripts that the Virtual Machines continuously monitor. + + * Control.ps1: the control script the VMs use to implement the control loop (what used to be called “master.ps1”). + + * Run.ps1: The VMs continuously look for the most recent version of run.ps1 and runs the newly updated script (parameters). + + * Collect/flag + + Location where the control script drops the “go”, “pause”, and “done” flag files. Users should not need to look at these files. + + * Collect/result + + Location of the output files from the VMFleet test run. + + * Collect/tools + + DiskSpd will be preinstalled in this folder. + +3. You need to create a new or use existing Resource group under the same subscription as the Resource bridge VM is under. Set-ArcConfig will take care of creating one if not already present. + +4. Setup configuration required for creating Arc enabled Virtual Machines. + +``` +Set-ArcConfig -ResourceGroup [ENTER_RESOURCE_GROUP] -AzureRegistrationUser [ENTER_AZURE_REGUSER] -AzureRegistrationPassword [ENTER_AZURE_REGPASS]-StoragePathCsv [ENTER_CSV_PATH] -Enabled $true -StoragePathName [ENTER_StoragePath_Name] -ImageName [ENTER_Image_Name] -ResetSalt +``` + + -"ResourceGroup" is the resource group where ARC VMs will be deployed. + + -"AzureRegistrationUser" and "AzureRegistrationPassword" are the azure account credentials. + + -"StoragePathCsv" (optional) is the csv path where storage path resource will be created. If not provided, one of the existing csvs which were created earlier will be used by default. + + -"StoragePathName" (optional) is the storage path name which will be used to create gallery image. If not provided, default name will be used. + + -"ImageName" (optional) is the gallery image name which will be used to create Arc-enabled virtual machines. If not provided, default name will be used. Currently, only windows gallery image is the supported image type for Arc-Enabled VMs. + + -"Enabled" (optional) is set to $false by default. Set to $true if Arc-enabled virtual machines are to be created. + + -"ResetSalt" (optional) is a flag used to reset salt. Salt is a random 4 character (alphanumeric) used in Arc resource names for arc enabled virtual machines (for eg: vm-group-node-salt-001). In case, user wants to regenerate the salt, they can run "Set-ArcConfig -ResetSalt" which will override existing salt with a new one in the arc.json and this will be used to create new VMs. This is useful in case of multiple clusters under same subscription using same resource group on a virtual setup. A 4 character (alphanumeric) Salt will be generated by default when user runs Set-ArcConfig the first time and stored in arc.json along with other arc configs. Within Set-ArcConfig, a quick test is done to check if atleast one vm exists with same salt in the resource group. If it does, it is regenerated. + +5. By default, VMs with 2GB static memory and 1 processor count will be created. + + Note: Please move the VHDX file into the collect folder. CSV Cache is also turned off by default. + + We will now create our “fleet” of VMs by running: + +``` +New-Fleet -basevhd -vms [ENTER_NUM_VMS] -adminpass [ENTER_ADMINPASS] -connectuser [ENTER_NODE_USER] -connectpass [ENTER_NODE_PASS] +``` + + -"adminpass" is the administrator password for the Server Core Image. This is the password you set on your Virtual Machine earlier. + + -"connectuser" is a domain account with access to the cluster. + + -"connectpass " is the password for the above domain account. + + -"vms" is the number of VM's to create per node. + +6. If "vms" parameter is not provided, the default is a 1:1 subscription ratio where the Number of VMs = Number of physical cores. + +[Optional] You can consider modifying the VM hardware configuration. Run + +``` +Set-Fleet -ProcessorCount 1 -MemoryStartupBytes 2048mb -MemoryMaximumBytes 2048mb -MemoryMinimumBytes 2048mb + ``` + +Note: + +If you specify “MemoryMaximumBytes”, you must specify “MemoryMinimumBytes”, which implies that your VMs will have dynamic memory. + +If you omit “MemoryMaximumBytes” or “MemoryMinimumBytes”, it implies that your VMs will have static memory. + +If MemoryStartupBytes = MemoryMinimumBytes = MemoryMaximumBytes, that also denotes static memory. + +“MemoryStartupBytes” is a mandatory parameter. + + +### Start Running VMFleet! + +7. Open 2 PowerShell terminals. In the first one, run Watch-Cluster and in the second one, run Start-Fleet. This second function will turn on all the VMs in a “paused” state. + +8. At this point you can run Start-FleetSweep [ENTER_PARAMETERS] or take this time to explore and run any of the other functions! + +Here is a sample sweep command to help you get started: "Start-FleetSweep -b 4 -t 8 -o 8 -w 0 -d 300 -p r" + +9. Done! + +### Aftermath + +Once you are done running VMFleet you can run Stop-Fleet to shut down all the virtual machines or run Remove-Fleet to completely delete all the virtual machines on your environment. + +# From Setup to Measure-FleetCoreWorkload + +This is a new workflow for setting up VMFleet and the predefined profile workloads (General, Peak, VDI, SQL). + + + +## Prerequisites + +Before we begin setting up VMFleet, there are a few prerequisites that you should have ready. + +Ensure an HCI cluster is setup. + +Ensure that you have 1 CSV per node + +Within Storage Spaces Direct, CPU usage is based on the host. Therefore, it is recommended that you split the storage load by creating as many CSVs as there are host nodes. We can go ahead and create a CSV per node in the cluster. + +In order to be precise about the CSV size, please use our new VMFleet command: "Get-FleetVolumeEstimate" + +This will output a prescribed CSV size based on different resiliency types. We recommend you select a 2-way mirrored value or 3-way mirrored value depending on your node count. + +1. You may run the following: (use the CSV size from Get-FleetVolumeEstimate) + +``` +Get-ClusterNode |% { + New-Volume -StoragePoolFriendlyName SU1_Pool* -FriendlyName $_ -FileSystem CSVFS_ReFS -Size +} +``` + + Ensure that you create a “collect” volume. + +2. You may run the following: + +``` +New-Volume -StoragePoolFriendlyName SU1_Pool* -FriendlyName collect -FileSystem CSVFS_ReFS -Size 200GB +``` + +3. If you have ran VMFleet in the past, please ensure that prior VMFleet directories are completely removed from existing volumes. + + Retrieve or install a Server Core VHDX file. If you do not have one handy, we can create a new one by following the below instructions. + + Download WS2019 Server Core ISO from the public website. + + Open Hyper-V Manager. + + Click “New”, then “Virtual Machine”. + + Navigate through the prompts and pick a location to store your VM. + + Once your VM is created, boot up the VM and follow the instructions. This is where you will decide your VM password, or what we will later call, “adminpass”. + + This is important as we will use this later, so make sure you write this down. + + Log out of the VM, and navigate to where you stored your VM. You should find a “Virtual Hard Disks” folder. Inside, you should find your new Server Core VHDX file. + Rename it to “Base1.vhdx”. + + Copy or move the file to the cluster environment that you want to run VMFleet in. + +4. Done! + +## Deployment +5. Let’s begin deploying VMFleet. First, we need to install the new PowerShell Module from the PowerShell Gallery and then load it into the terminal. Run the following: + +``` +Install-Module -Name “VMFleet” +Import-Module VMFleet +Set-ClusterStorageSpacesDirect -CacheState Disabled; +``` + + + +## Sanity Check: + +6. Run "Get-Module VMFleet" to confirm the module exists. + +7. Run "Get-Command -Module VMFleet" to obtain a list of commands included in the module. + +8. We will now set up the directory structure within the “Collect” CSV that we created earlier. Run "Install-Fleet" + +9. You need to create a new or use existing Resource group under the same subscription as the Resource bridge VM is under. Set-ArcConfig will take care of creating one if not already present. + +10. Setup configuration required for creating Arc enabled Virtual Machines. + +``` +Set-ArcConfig -ResourceGroup [ENTER_RESOURCE_GROUP] -AzureRegistrationUser [ENTER_AZURE_REGUSER] -AzureRegistrationPassword [ENTER_AZURE_REGPASS]-StoragePathCsv [ENTER_CSV_PATH] -Enabled $true -StoragePathName [ENTER_StoragePath_Name] -ImageName [ENTER_Image_Name] -ResetSalt +``` + + -"ResourceGroup" is the resource group where ARC VMs will be deployed. + + -"AzureRegistrationUser" and "AzureRegistrationPassword" are the azure account credentials. + + -"StoragePathCsv" (optional) is the csv path where storage path resource will be created. If not provided, one of the existing csvs which were created earlier will be used by default. + + -"StoragePathName" (optional) is the storage path name which will be used to create gallery image. If not provided, default name will be used. + + -"ImageName" (optional) is the gallery image name which will be used to create Arc-enabled virtual machines. If not provided, default name will be used. + + -"Enabled" (optional) is set to $false by default. Set to $true if Arc-enabled virtual machines are to be created. + + -"ResetSalt" (optional) is a flag used to reset salt. Salt is a random 4 character (alphanumeric) used in Arc resource names for arc enabled virtual machines (for eg: vm-group-node-salt-001). In case, user wants to regenerate the salt, they can run "Set-ArcConfig -ResetSalt" which will override existing salt with a new one in the arc.json and this will be used to create new VMs. This is useful in case of multiple clusters under same subscription using same resource group on a virtual setup. A 4 character (alphanumeric) Salt will be generated by default when user runs Set-ArcConfig the first time and stored in arc.json along with other arc configs. Within Set-ArcConfig, a quick test is done to check if atleast one vm exists with same salt in the resource group. If it does, it is regenerated. + +11. By default, VMs with 2GB static memory and 1 processor count will be created. + + Note: Please move the VHDX file into the collect folder. CSV Cache is also turned off by default. + + We will now create our “fleet” of VMs by running: + +``` +New-Fleet -basevhd -vms [ENTER_NUM_VMS] -adminpass [ENTER_ADMINPASS] -connectuser [ENTER_NODE_USER] -connectpass [ENTER_NODE_PASS] +``` + + -"adminpass" is the administrator password for the Server Core Image. This is the password you set on your Virtual Machine earlier. + + -"connectuser" is a domain account with access to the cluster. + + -"connectpass " is the password for the above domain account. + + -"vms" is the number of VM's to create per node. + + +12. Measure-FleetCoreWorkload also collects diagnostic data (Get-SDDCDiagnosticInfo). Therefore, before running the command, we must also install the NuGet Package if you have not previously done so. In doing so, we also need to temporairly set the PSGallery as a trusted repository source (Note: This will temporarily relax the security boundary). + + +``` +$repo = Get-PSRepository -Name PSGallery +if ($null -eq $repo) { Write-Host "The PSGallery is not configured on this system, please address this before continuing" } +else { + if ($repo.InstallationPolicy -ne 'Trusted') { + Write-Host "Setting the PSGallery repository to Trusted, original InstallationPolicy: $($repo.InstallationPolicy)" + Set-PSRepository -Name PSGallery -InstallationPolicy Trusted + } + + ### Installing the pre-requisite modules + + Install-PackageProvider NuGet -Force + Install-Module -Name PrivateCloud.DiagnosticInfo -Force + Install-Module -Name MSFT.Network.Diag -Force + + if ($repo.InstallationPolicy -ne 'Trusted') { + Write-Host "Resetting the PSGallery repository to $($repo.InstallationPolicy)" + Set-PSRepository -Name PSGallery -InstallationPolicy $repo.InstallationPolicy + } +} +``` + +## Run Measure-FleetCoreWorkload! +13. We can now run Measure-FleetCoreWorkload. Running the command below will automatically run all 4 workloads (General, Peak, VDI, SQL) and place the individual outputs in the result directory. IMPORTANT: If you plan on running another test, please clear the result directory. + + +``` +Measure-FleetCoreWorkload +``` + +14. Congratulations! You’re done! All you need to do is wait for the test to complete. + +Note: If you ever run into an error and need to rerun Measure-FleetCoreWorkload, don’t be afraid to do so! It is smart enough to pick up from where it last stopped and continue the test without starting from scratch. diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/Profile.psm1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/Profile.psm1 new file mode 100644 index 0000000..9fefe39 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/Profile.psm1 @@ -0,0 +1,1031 @@ +<# +VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +# +# Template Profiles +# +# Template profiles state througput in relative units per target per timespan. +# If a timespan contains two targets, one with 1 relative IOPS and one with 9 +# relative IOPS, applying an aggregate IOPS throughput of 100 would result in +# 10 & 90 respectively. +# +# Throughput MUST be in template in order to apply throughput limits. +# + +# Note: random/stridesize elements MUST be removed if -Random will be allowed for +# a profile. + +# +# Peak/General: a single thread high-QD unbuffered/writethrough single target template +# with latency/iops measured. 10MiB random write data source. All substitutions +# allowed. +# +# Peak and General differ in distribution. General is across a non-uniform distribution +# of the entire available workingset, while Peak has no built-in distribution. This allows +# Peak to be instantiated with a reduced workingset (-BaseOffset/-MaxOffset) for a uniformly +# loaded small workingset. +# +# +# Derived from: -t1 -b4k -o32 -w0 -r -Suw -D -L -Z10m *1 +# Random element removed, also BaseFileOffset/MaxFileSize +# Profile specific required parameters override WriteRatio, BlockSize +# + +$PeakProfile = @" + + 0 + xml + false + + + false + true + true + false + + + + 1000 + 0 + + + *1 + 4096 + true + true + + random + + 10485760 + + + 0 + 32 + 0 + 0 + 1 + 3 + + + + + +"@ + +$GeneralProfile = @" + + 0 + xml + false + + + false + true + true + false + + + + 1000 + 0 + + + *1 + 4096 + true + true + + random + + 10485760 + + + + + 5 + 10 + + + 0 + 32 + 0 + 0 + 1 + 3 + + + + + +"@ + +# VDI Profile: DiskSpd parameters that mimic a VDI workload +# +# Derived from: +# (write component) -t1 -o8 -b32k -w100 -g6i -rs20 -rdpct95/5:4/10 -Z10m -f10g *1 +# (read component) -t1 -o8 -b8k -w0 -g6i -rs80 -rdpct95/5:4/10 -f8g *1 + + +$VDIProfile = @" + + 0 + xml + false + + + false + true + true + false + + + 0 + 1000 + 0 + + + *1 + 32768 + 0 + 10737418240 + true + true + + random + + 10485760 + + + 32768 + 20 + + + 5 + 10 + + + 0 + 8 + 100 + 6 + 1 + 3 + + + *1 + 8192 + 0 + 8589934592 + true + true + + sequential + + 8192 + 80 + + + 5 + 10 + + + 0 + 8 + 0 + 6 + 1 + 3 + + + + + +"@ + +# SQL Profile: DiskSpd parameters that mimic an SQL workload with log transaction +# +# Derived from: +# (OLTP) -t4 -o32 -r -b8k -g1500i -w30 -rdpct95/5:4/10 -B5g -Z10m *1 +# (Log) -t1 -o1 -s -b32k -g300i -w100 -f5g -Z10m *1 + +$SQLProfile = @" + + 0 + xml + false + + + false + true + true + false + + + + 1000 + 0 + + + *1 + 8192 + 5368709120 + 0 + true + true + + random + + 10485760 + + + 8192 + + + 5 + 10 + + + 0 + 32 + 30 + 1500 + 4 + 3 + + + *1 + 32768 + 0 + 5368709120 + true + true + + random + + 10485760 + + + 32768 + 0 + 1 + 100 + 300 + 1 + 3 + + + + + +"@ + +# +# Requires - parameters which must be provided +# AllowsRandSeq - compatibility with -Random/-Sequential rewrite rule +# Compatibility - list of parameters for compatibility check +# Compatible - provides sense of the Compatibility list for simpler bulk inclusion/exclusion +# true - an inclusion list; parameters not mentioned are not allowed (empty = none allowed) +# false - an exclusion list; parameters mentioned are not allowed (empty = all allowed) +# + +$FleetProfiles = @{ + Peak = @{ + Profile = $PeakProfile + AllowRandSeq = $true + Requires = @('WriteRatio', 'BlockSize') + Compatibility = $null + Compatible = $false + } + + General = @{ + Profile = $GeneralProfile + AllowRandSeq = $true + Requires = @('WriteRatio', 'BlockSize') + Compatibility = $null + Compatible = $false + } + + VDI = @{ + Profile = $VDIProfile + AllowRandSeq = $false + Requires = $null + Compatibility = $null + Compatible = $true + } + + SQL = @{ + Profile = $SQLProfile + AllowRandSeq = $false + Requires = $null + Compatibility = $null + Compatible = $true + } +} + +function Get-FleetProfileXml +{ + [CmdletBinding()] + param( + [Parameter()] + [string] + $Name, + + [Parameter()] + [uint32] + $Warmup = 300, + + [Parameter()] + [uint32] + $Duration = 60, + + [Parameter()] + [uint32] + $Cooldown = 30, + + [ValidateRange(0, 100)] + [uint32] + $WriteRatio, + + [Parameter()] + [uint32] + $ThreadsPerTarget, + + [Parameter()] + [uint32] + $BlockSize, + + [Parameter()] + [uint32] + $Alignment, + + [Parameter()] + [switch] + $Random, + + [Parameter()] + [switch] + $Sequential, + + [ValidateRange(0, 100)] + [uint32] + $RandomRatio, + + [Parameter()] + [uint32] + $RequestCount, + + [Parameter()] + [uint64] + $ThreadStride, + + [Parameter()] + [uint64] + $BaseOffset, + + [Parameter()] + [uint64] + $MaxOffset, + + [Parameter()] + [ValidateRange(1,60)] + [uint32] + $IoBucketDurationSeconds + ) + + $x = $null + + # + # Get base profile and validate profile-specific paramters + # + + if (-not $PSBoundParameters.ContainsKey('Name')) + { + Write-Error "Available profiles: $(@($FleetProfiles.Keys | Sort-Object) -join ', ')" + return + } + elseif (-not $FleetProfiles.ContainsKey($Name)) + { + Write-Error "Unknown profile $Name; available: $(@($FleetProfiles.Keys | Sort-Object) -join ', ')" + return + } + + # + # Switch validation + # + + if ($PsBoundParameters.ContainsKey('Sequential') -and $PsBoundParameters.ContainsKey('Random')) + { + Write-Error "Random and Sequential cannot be specified together" + return + } + + # + # Requires check + # + + $err = @() + foreach ($arg in $FleetProfiles[$Name].Requires) + { + if (-not $PSBoundParameters.ContainsKey($arg)) + { + $err += ,$arg + } + } + + if ($err.Count) + { + Write-Error "$Name profile requires specification of $($err -join ', ')" + return + } + + # + # Incompatible check + # + + if (-not ($FleetProfiles[$Name].Compatible)) + { + foreach ($arg in $FleetProfiles[$Name].Compatibility) + { + if ($PSBoundParameters.ContainsKey($arg)) + { + $err += ,$arg + } + } + } + + # + # Compatible check + # + + else + { + # Always-on core parameters + required list + compatible list + $baseParameters = @('Name', 'Warmup', 'Duration', 'Cooldown') + + foreach ($arg in $PSBoundParameters.Keys) + { + if ($baseParameters -notcontains $arg -and + $FleetProfiles[$Name].Requires -notcontains $arg -and + $FleetProfiles[$Name].Compatibility -notcontains $arg) + { + $err += ,$arg + } + } + } + + if ($err.Count) + { + Write-Error "$Name profile does not allow specification of $($err -join ', ')" + return + } + + # + # Now load/modify profile. + # + + $x = [xml] $FleetProfiles[$Name].Profile + + # + # Perform replacements @ TimeSpan + # + + foreach ($timeSpan in $x.SelectNodes("Profile/TimeSpans/TimeSpan")) + { + $timeSpan.Warmup = [string] $Warmup + $timeSpan.Duration = [string] $Duration + $timeSpan.Cooldown = [string] $Cooldown + + if ($PSBoundParameters.ContainsKey('IoBucketDurationSeconds')) + { + $timeSpan.IoBucketDuration = [string] ($IoBucketDurationSeconds * 1000) + } + + # Autoscale to best clock fit >1000 n-second IOPS bucket datapoints. + else + { + $timeSpan.IoBucketDuration = [string] ((FitClockRate -TotalSeconds $Duration -Samples 1000) * 1000) + } + } + + # + # Perform replacements @ Target + # + + foreach ($targetSet in $x.SelectNodes("Profile/TimeSpans/TimeSpan/Targets")) + { + $targets = $targetSet.SelectNodes("Target") + + foreach ($target in $targets) + { + # + # 1:1 Common Substitutions + # + + $TargetSubstitutions = @{ + BlockSize = 'BlockSize' + RequestCount = 'RequestCount' + ThreadsPerTarget = 'ThreadsPerFile' + ThreadStride = 'ThreadStride' + WriteRatio = 'WriteRatio' + MaxOffset = 'MaxFileSize' + BaseOffset = 'BaseFileOffset' + } + + foreach ($s in $TargetSubstitutions.Keys) + { + if ($PSBoundParameters.ContainsKey($s)) + { + SetSingleNode -Xml $x -ParentNode $target -Node $TargetSubstitutions[$s] -Value ([string] $PSBoundParameters[$s]) + } + } + + # + # Target Random/Sequential/Alignment + # + + if ($FleetProfiles[$Name].AllowRandSeq) + { + # Inherit default buffer alignment from buffer size + if (-not $PSBoundParameters.ContainsKey('Alignment')) + { + $Alignment = $BlockSize + } + + # Random or RandomRatio + if ($Random -or $PsBoundParameters.ContainsKey('RandomRatio')) + { + $e = $x.CreateNode([xml.xmlnodetype]::Element, 'Random', '') + $e.InnerText = [string] $Alignment + $null = $target.AppendChild($e) + + if ($PsBoundParameters.ContainsKey('RandomRatio')) + { + $e = $x.CreateNode([xml.xmlnodetype]::Element, 'RandomRatio', '') + $e.InnerText = [string] $RandomRatio + $null = $target.AppendChild($e) + } + } + + # Sequential (default if neither specified) + elseif ($Sequential -or -not $Random) + { + $e = $x.CreateNode([xml.xmlnodetype]::Element, 'StrideSize', '') + $e.InnerText = [string] $Alignment + $null = $target.AppendChild($e) + + # + # Remove any distribution attached to the target - not compatibile (or relevant); + # sequential is sequential. + # + + $dists = $target.SelectNodes("Distribution") + foreach ($dist in $dists) + { + $null = $target.RemoveChild($dist) + } + } + } + } + } + + $x +} + +function Set-FleetProfile +{ + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline = $true, Mandatory = $true)] + [xml] + $ProfileXml, + + [Parameter()] + [uint32] + $Throughput, + + [Parameter()] + [ValidateSet("IOPS","BPMS")] + [string] + $ThroughputUnit = "IOPS", + + [Parameter()] + [uint32] + $Warmup, + + [Parameter()] + [uint32] + $Duration, + + [Parameter()] + [uint32] + $Cooldown + ) + + process + { + $x = $ProfileXml.Clone() + + foreach ($timeSpan in $x.SelectNodes("Profile/TimeSpans/TimeSpan")) + { + if ($PSBoundParameters.ContainsKey('Warmup')) + { + $timeSpan.Warmup = [string] $Warmup + } + + if ($PSBoundParameters.ContainsKey('Cooldown')) + { + $timeSpan.Cooldown = [string] $Cooldown + } + + if ($PSBoundParameters.ContainsKey('Duration')) + { + $timeSpan.Duration = [string] $Duration + } + + if ($PSBoundParameters.ContainsKey('Throughput')) + { + # Fail if timespan is in threadpool form - DISKSPD does not support + # throughput in this case. + + $t = $timeSpan.SelectSingleNode("ThreadCount") + if ($null -ne $t -and $t.InnerText -ne 0) + { + throw "Cannot set bounded throughput on profile using thread pool (-F/TimeSpan ThreadCount)" + } + + foreach ($targetSet in $timeSpan.SelectNodes("Targets")) + { + # + # Pass 1 - Total + # + + $total = [uint32] 0 + $nTargets = 0 + foreach ($target in $targetSet.Target) + { + $thisTput = 0 + $e = $target.SelectSingleNode("Throughput") + if ($null -ne $e) + { + $thisTput = [uint32] $e.InnerText + + $e = $target.SelectSingleNode("ThreadsPerFile") + if ($null -eq $e -or ([uint32] $e.InnerText) -eq 0) + { + throw "ThreadsPerFile is not present - zero or absent - to scale Throughput (target $nTargets)" + } + + $thisTput *= [uint32] $e.InnerText + } + + if (($total -ne 0 -and $thisTput -eq 0) -or + ($total -eq 0 -and $thisTput -ne 0 -and $nTargets -ne 0)) + { + throw "Cannot set throughput on profile with a combination of bounded/unbounded targets (target $nTargets)" + } + + $total += $thisTput + ++$nTargets + } + + # If there is no throughout specified (unbounded) and no ratio specified + # in the profile, we are done - already unbounded. + + if ($Throughput -eq 0 -and $total -eq 0) { continue } + + # + # Pass 2a - Distribute nonzero by ratio, as well as zero to single target + # + + if ($Throughput -ne 0 -or $targets.Count -eq 1) + { + foreach ($target in $targetSet.Target) + { + + # Distribute equally + if ($total -eq 0) + { + SetSingleNode -Xml $x -ParentNode $target -Node 'Throughput' -Value ([int] ($Throughput / $nTargets)) + $e = $target.SelectSingleNode("Throughput") + } + + # Distribute proportionally + # Note: in absolute terms the numerator $thisTput should be scaled by #threads to arrive at + # a fraction the new throughput, but then we would immediatly divide #threads out of the result + # to arrive at per thread throughput; this can be avoided. + else + { + $e = $target.SelectSingleNode("Throughput") + $thisTput = [uint32] $e.InnerText + $e.InnerText = [string] [int] ($Throughput * ($thisTput / $total)) + } + + $e.SetAttribute('unit', $ThroughputUnit) + } + } + + # Pass 2b - Distribute "zero" across multiple by converting target threads to pool + # with weighted ratio of throughputs on targets. Set interlocked sequential + # on sequential targets unless a nonzero threadstride is already present. + # This can result in a close approximation of unbounded result on the tput + # limited specification without needing dynamic scale-up, but should be used + # with caution. + else + { + # Loop targets to count threads, move tputs to weights and set interlocked sequential. + + $nThreads = 0 + foreach ($target in $targetSet.Target) + { + # Count threads + $e = $target.SelectSingleNode("ThreadsPerFile") + if ($null -eq $e -or $e.InnerText -eq 0) + { + throw "Invalid -t/ThreadsPerFile specification (absent or zero) converting to unbounded form" + } + $thisThreads = [uint32] $e.InnerText + $nThreads += $thisThreads + + # Remove ThreadsPerFile/RequestCount @ Target + $null = $target.RemoveChild($e) + $e = $target.SelectSingleNode("RequestCount") + if ($null -ne $e) { $null = $target.RemoveChild($e) } + + # Move Throuhput to Weight. Note - Throughput guaranteed to exist or we would have + # returned at the 0/0 check. Weight scales by number of threads on the target. + $e = $target.SelectSingleNode("Throughput") + $thisTput = [uint32] $e.InnerText + $null = $target.RemoveChild($e) + + SetSingleNode -Xml $x -ParentNode $target -Node 'Weight' -Value ($thisTput * $thisThreads) + + # Sequential target without ThreadStride? Move to interlocked. + $e = $target.SelectSingleNode("StrideSize") + if ($null -ne $e) + { + $e = $target.SelectSingleNode("ThreadStride") + if ($null -eq $e -or $e.InnerText -eq 0) + { + SetSingleNode -Xml $x -ParentNode $target -Node 'InterlockedSequential' -Value 'true' + } + } + } + + # Now set TimeSpan level thread pool with high request count + + SetSingleNode -Xml $x -ParentNode $timeSpan -Node 'ThreadCount' -Value $nThreads + SetSingleNode -Xml $x -ParentNode $timeSpan -Node 'RequestCount' -Value 32 + } + } + } + } + + $x + } +} +function GetFleetProfileFootprint +{ + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline = $true, Mandatory = $true)] + [xml] + $ProfileXml, + + [Parameter()] + [switch] + $Read + ) + + $r = @{} + + # Capture min base offset and max max offset across timespans for each target. + # Note that 0 max offset indicates it is not bounded (bounded by target size). + + foreach ($target in $ProfileXml.SelectNodes("Profile/TimeSpans/TimeSpan/Targets/Target")) + { + # Skip write-only target specs if only interested in read workingset + if ($Read -and $target.WriteRatio -eq '100') + { + continue + } + + $bo = [uint64](GetSingleNode $target 'BaseFileOffset') + $mo = [uint64](GetSingleNode $target 'MaxFileSize') + + $node = $r[$target.Path] + + if ($null -ne $node) + { + if ($node.BaseOffset -gt $bo) + { + $r[$target.Path].BaseOffset = $bo + } + + if ($mo -eq 0) + { + $r[$target.Path].MaxOffset = 0 + } + elseif ($node.MaxOffset -ne 0 -and $node.MaxOffset -lt $mo) + { + $r[$target.Path].MaxOffset = $mo + } + } + else + { + $r[$target.Path] = [pscustomobject] @{ + BaseOffset = $bo + MaxOffset = $mo + } + } + } + + $r +} + +function Convert-FleetXmlToString +{ + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline = $true, Mandatory = $true)] + [object] + $InputObject + ) + + process { + + switch ($InputObject.GetType().FullName) + { + "System.Xml.XmlDocument" { break } + "System.Xml.XmlElement" { break } + default { + throw "Unknown object type $_ - must be XmlDocument or XmlElement" + } + } + + $sw = [System.IO.StringWriter]::new() + $xo = [System.Xml.XmlTextWriter]::new($sw) + $xo.Formatting = [System.Xml.Formatting]::Indented + + $InputObject.WriteTo($xo) + $sw.ToString() + } +} + +function FitClockRate +{ + param( + [ValidateRange(1,(1000*60*60))] + [uint32] + $TotalSeconds, + + [uint32] + $Samples + ) + + # Perform a best-fit for the longest interval which produces at least $Samples over + # the given $TotalSeconds time period and evenly divide minutes/hours. E.g.: + # 1..60 |? { 60 % $_ -eq 0 }) + # + # Range is sanity capped at 1000h. + + $div = 1,2,3,4,5,6,10,12,15,20,30,60 + $last = 1 + + # First multiple is seconds, second produces minutes. + + foreach ($mul in (1,60)) + { + foreach ($d in $div) + { + # Skip 60s and roll over to 1 minute. + # 60 is only used for minutes. (e.g., 1hr) + if ($mul -eq 1 -and $d -eq 60 ) { break } + + if (($TotalSeconds / ($d * $mul)) -lt $Samples) + { + return $last + } + + $last = $d * $mul + } + } + + return $last +} +function SetSingleNode +{ + param( + [xml] + $Xml, + + [xml.xmlelement] + $ParentNode, + + [string] + $Node, + + [string] + $Value + ) + + # Set/Create child node to given value + + $e = $ParentNode.SelectSingleNode($Node) + if ($null -ne $e) + { + $e.InnerText = $Value + } + else + { + $e = $Xml.CreateNode([xml.xmlnodetype]::Element, $Node, '') + $e.InnerText = $Value + $null = $ParentNode.AppendChild($e) + } +} + +function GetSingleNode +{ + param( + [xml.xmlelement] + $ParentNode, + + [string] + $Node + ) + + # Gete child node, if present + + $e = $ParentNode.SelectSingleNode($Node) + if ($null -ne $e) + { + return $e.InnerText + } + + return $null +} + +function IsProfileSingleTimespan +{ + param( + [xml] + $ProfileXml + ) + + $ts = @($ProfileXml.SelectNodes("Profile/TimeSpans/TimeSpan")) + return ($ts.Count -eq 1) +} + +function IsProfileThroughputLimited +{ + param( + [xml] + $ProfileXml + ) + + $tputs = @($ProfileXml.SelectNodes("Profile/TimeSpans/TimeSpan/Targets/Target/Throughput")) + + foreach ($t in $tputs) + { + if (([uint32]$t.InnerText) -ne 0) + { + return $true + } + } + + return $false +} + +function IsProfileSingleTarget +{ + param( + [xml] + $ProfileXml + ) + + $tgts = @($ProfileXml.SelectNodes("Profile/TimeSpans/TimeSpan/Targets/Target")) + return ($tgts.Count -eq 1) +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/VMFleet.Tests.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/VMFleet.Tests.ps1 new file mode 100644 index 0000000..1c050cb --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/VMFleet.Tests.ps1 @@ -0,0 +1,728 @@ +Describe "TestModulePresent" { + + BeforeAll { + + $script:e = $null + } + + if (Test-Path .\VMFleet.psd1) + { + It "ShouldLoad-Local" { + { Import-Module .\VMFleet.psd1 -Force -ErrorVariable script:e } | Should Not Throw + $script:e | Should BeNullOrEmpty Because "this should be successful" + } + } + else + { + It "ModuleExists-Installed" { + Get-Module VMFleet -ListAvailable -ErrorVariable script:e | Should Not BeNullOrEmpty Because "module should be installed on system" + $script:e | Should BeNullOrEmpty Because "this should be successful" + } + + It "ShouldLoad-Installed" { + { Import-Module VMFleet -Force -ErrorVariable script:e } | Should Not Throw + $script:e | Should BeNullOrEmpty Because "this should be successful" + } + } + + It "ShouldBeLoaded" { + Get-Module VMFleet -ErrorVariable script:e | Should Not BeNullOrEmpty Because "module should now be loaded" + $script:e | Should BeNullOrEmpty Because "this should be successful" + } +} + +InModuleScope VMFleet { + + Describe "GetDistributedShift" { + + BeforeAll { + + $a = @('a', 'b', 'c', 'd', 'e') + $b = @('f', 'g', 'h', 'i', 'j') + $c = @('k', 'l', 'm', 'n', 'o') + $d = @('p', 'q', 'r', 's', 't') + + $bad = @('z') + + $a20 = 1..20 |% { "a$_" } + $b20 = 1..20 |% { "b$_" } + $c20 = 1..20 |% { "c$_" } + $d20 = 1..20 |% { "d$_" } + } + + It "ShouldRequireListOfLists" { + GetDistributedShift -Group $a -N 1 | Should Throw + } + + It "ShouldRequireMoreThanOne" { + GetDistributedShift -Group @($a) -N 1 | Should Throw + } + + It "ShouldRequireAllSameSize" { + GetDistributedShift -Group $a,$b,$bad -N 1 | Should Throw + } + + It "ShouldRequirePositiveRotation" { + GetDistributedShift -Group $a,$b -N 0 | Should Throw + } + + It "ShouldNotShiftTooMany" { + GetDistributedShift -Group $a,$b -N 6 | Should Throw + } + + # + # Two group rotations + # + + It "Rotate2-1" { + { GetDistributedShift -Group $a,$b -N 1 } | Should Not Throw + $a1, $b1 = GetDistributedShift -Group $a,$b -N 1 + $a1.Count | Should Be 5 + $b1.Count | Should Be 5 + + ($a1 -join '') | Should Be "abcdj" + ($b1 -join '') | Should Be "fghie" + } + + It "Rotate2-2" { + { GetDistributedShift -Group $a,$b -N 2 } | Should Not Throw + $a1, $b1 = GetDistributedShift -Group $a,$b -N 2 + $a1.Count | Should Be 5 + $b1.Count | Should Be 5 + + ($a1 -join '') | Should Be "abcij" + ($b1 -join '') | Should Be "fghde" + } + + It "Rotate2-3" { + { GetDistributedShift -Group $a,$b -N 3 } | Should Not Throw + $a1, $b1 = GetDistributedShift -Group $a,$b -N 3 + $a1.Count | Should Be 5 + $b1.Count | Should Be 5 + + ($a1 -join '') | Should Be "abhij" + ($b1 -join '') | Should Be "fgcde" + } + + It "Rotate2-4" { + { GetDistributedShift -Group $a,$b -N 4 } | Should Not Throw + $a1, $b1 = GetDistributedShift -Group $a,$b -N 4 + $a1.Count | Should Be 5 + $b1.Count | Should Be 5 + + ($a1 -join '') | Should Be "aghij" + ($b1 -join '') | Should Be "fbcde" + } + + It "Rotate2-5" { + { GetDistributedShift -Group $a,$b -N 5 } | Should Not Throw + $a1, $b1 = GetDistributedShift -Group $a,$b -N 5 + $a1.Count | Should Be 5 + $b1.Count | Should Be 5 + + ($a1 -join '') | Should Be "fghij" + ($b1 -join '') | Should Be "abcde" + } + + # + # Three group rotations + # + + It "Rotate3-1" { + { GetDistributedShift -Group $a,$b,$c -N 1 } | Should Not Throw + $a1, $b1, $c1 = GetDistributedShift -Group $a,$b,$c -N 1 + $a1.Count | Should Be 5 + $b1.Count | Should Be 5 + $c1.Count | Should Be 5 + + ($a1 -join '') | Should Be "abcdo" + ($b1 -join '') | Should Be "fghie" + ($c1 -join '') | Should Be "klmnj" + } + + It "Rotate3-2" { + { GetDistributedShift -Group $a,$b,$c -N 2 } | Should Not Throw + $a1, $b1, $c1 = GetDistributedShift -Group $a,$b,$c -N 2 + $a1.Count | Should Be 5 + $b1.Count | Should Be 5 + $c1.Count | Should Be 5 + + ($a1 -join '') | Should Be "abcio" + ($b1 -join '') | Should Be "fghne" + ($c1 -join '') | Should Be "klmdj" + } + + It "Rotate3-3" { + { GetDistributedShift -Group $a,$b,$c -N 3 } | Should Not Throw + $a1, $b1, $c1 = GetDistributedShift -Group $a,$b,$c -N 3 + $a1.Count | Should Be 5 + $b1.Count | Should Be 5 + $c1.Count | Should Be 5 + + ($a1 -join '') | Should Be "abmio" + ($b1 -join '') | Should Be "fgcne" + ($c1 -join '') | Should Be "klhdj" + } + + It "Rotate3-4" { + { GetDistributedShift -Group $a,$b,$c -N 4 } | Should Not Throw + $a1, $b1, $c1 = GetDistributedShift -Group $a,$b,$c -N 4 + $a1.Count | Should Be 5 + $b1.Count | Should Be 5 + $c1.Count | Should Be 5 + + ($a1 -join '') | Should Be "agmio" + ($b1 -join '') | Should Be "flcne" + ($c1 -join '') | Should Be "kbhdj" + } + + It "Rotate3-5" { + { GetDistributedShift -Group $a,$b,$c -N 5 } | Should Not Throw + $a1, $b1, $c1 = GetDistributedShift -Group $a,$b,$c -N 5 + $a1.Count | Should Be 5 + $b1.Count | Should Be 5 + $c1.Count | Should Be 5 + + ($a1 -join '') | Should Be "kgmio" + ($b1 -join '') | Should Be "alcne" + ($c1 -join '') | Should Be "fbhdj" + } + + It "RotateLarge4-1" { + { GetDistributedShift -Group $a20,$b20,$c20,$d20 -N 1 } | Should Not Throw + $g = GetDistributedShift -Group $a20,$b20,$c20,$d20 -N 1 + + # note last group rotates 1 + $g[0] -join '' | Should Be "a1a2a3a4a5a6a7a8a9a10a11a12a13a14a15a16a17a18a19d20" + $g[1] -join '' | Should Be "b1b2b3b4b5b6b7b8b9b10b11b12b13b14b15b16b17b18b19a20" + $g[2] -join '' | Should Be "c1c2c3c4c5c6c7c8c9c10c11c12c13c14c15c16c17c18c19b20" + $g[3] -join '' | Should Be "d1d2d3d4d5d6d7d8d9d10d11d12d13d14d15d16d17d18d19c20" + } + + It "RotateLarge4-2" { + { GetDistributedShift -Group $a20,$b20,$c20,$d20 -N 2 } | Should Not Throw + $g = GetDistributedShift -Group $a20,$b20,$c20,$d20 -N 2 + + # note last group rotates 1, next 2 + $g[0] -join '' | Should Be "a1a2a3a4a5a6a7a8a9a10a11a12a13a14a15a16a17a18c19d20" + $g[1] -join '' | Should Be "b1b2b3b4b5b6b7b8b9b10b11b12b13b14b15b16b17b18d19a20" + $g[2] -join '' | Should Be "c1c2c3c4c5c6c7c8c9c10c11c12c13c14c15c16c17c18a19b20" + $g[3] -join '' | Should Be "d1d2d3d4d5d6d7d8d9d10d11d12d13d14d15d16d17d18b19c20" + } + + It "RotateLarge4-3" { + { GetDistributedShift -Group $a20,$b20,$c20,$d20 -N 3 } | Should Not Throw + $g = GetDistributedShift -Group $a20,$b20,$c20,$d20 -N 3 + + # note last group rotates 1, then 2, 3 + $g[0] -join '' | Should Be "a1a2a3a4a5a6a7a8a9a10a11a12a13a14a15a16a17b18c19d20" + $g[1] -join '' | Should Be "b1b2b3b4b5b6b7b8b9b10b11b12b13b14b15b16b17c18d19a20" + $g[2] -join '' | Should Be "c1c2c3c4c5c6c7c8c9c10c11c12c13c14c15c16c17d18a19b20" + $g[3] -join '' | Should Be "d1d2d3d4d5d6d7d8d9d10d11d12d13d14d15d16d17a18b19c20" + } + + It "RotateLarge4-4" { + { GetDistributedShift -Group $a20,$b20,$c20,$d20 -N 4 } | Should Not Throw + $g = GetDistributedShift -Group $a20,$b20,$c20,$d20 -N 4 + + # note last group rotates 1, then 2, 3, and back to 1 + $g[0] -join '' | Should Be "a1a2a3a4a5a6a7a8a9a10a11a12a13a14a15a16d17b18c19d20" + $g[1] -join '' | Should Be "b1b2b3b4b5b6b7b8b9b10b11b12b13b14b15b16a17c18d19a20" + $g[2] -join '' | Should Be "c1c2c3c4c5c6c7c8c9c10c11c12c13c14c15c16b17d18a19b20" + $g[3] -join '' | Should Be "d1d2d3d4d5d6d7d8d9d10d11d12d13d14d15d16c17a18b19c20" + } + } + + Describe "FilterObject" { + + BeforeAll { + $o12 = [PSCustomObject]@{ + K1 = 1 + K2 = 2 + } + $o13 = [PSCustomObject]@{ + K1 = 1 + K2 = 3 + } + } + + It "PassWithEmpty" { + $o12 | FilterObject -Filter @{} | Should Not BeNullOrEmpty + } + It "PassWithOneK" { + $o12 | FilterObject -Filter @{ K1 = 1 } | Should Not BeNullOrEmpty + } + It "PassWithTwoK" { + $o12 | FilterObject -Filter @{ K1 = 1; K2 = 2 } | Should Not BeNullOrEmpty + } + It "PassWithOneKDiffType" { + $o12 | FilterObject -Filter @{ K1 = '1' } | Should Not BeNullOrEmpty + } + It "PassWithTwoKDiffType" { + $o12 | FilterObject -Filter @{ K1 = '1'; K2 = '2' } | Should Not BeNullOrEmpty + } + It "NotPassMismatchSameType" { + $o12 | FilterObject -Filter @{ K1 = 2 } | Should BeNullOrEmpty + } + It "NotPassMismatchDiffType" { + $o12 | FilterObject -Filter @{ K1 = '2' } | Should BeNullOrEmpty + } + + It "ShouldPassMultipleMatch" { + $r = @($o12,$o12 | FilterObject -Filter @{ K1 = 1 }) + $r.Count | Should Be 2 + } + + It "ShouldPassTheMatch" { + $r = @($o12,$o13 | FilterObject -Filter @{ K2 = 2 }) + $r.Count | Should Be 1 + $r.K1 | Should Be 1 + $r.K2 | Should Be 2 + } + } +} + +Describe "SetFleetProfile" { + + BeforeAll { + + It "ShouldBeExpectedSqlProfile" { + { Get-FleetprofileXml -Name SQL } | Should Not Throw + $x = Get-FleetprofileXml -Name SQL + + $x.Profile.TimeSpans.TimeSpan.Targets.Target[0].Throughput.InnerText | Should Be 1500 + $x.Profile.TimeSpans.TimeSpan.Targets.Target[1].Throughput.InnerText | Should Be 300 + } + + $x = Get-FleetprofileXml -Name SQL + + $oWarmup = $x.Profile.TimeSpans.TimeSpan.Warmup + $oDuration = $x.Profile.TimeSpans.TimeSpan.Duration + $oCooldown = $x.Profile.TimeSpans.TimeSpan.Cooldown + } + + It "ShouldSetByParam" { + { Set-FleetProfile -ProfileXml $x -Throughput 1000 } | Should Not Throw + $xn = Set-FleetProfile -ProfileXml $x -Throughput 1000 + + # original composition is 1500*4 threads + 300*1 thread = 6300 + # 1000 * 6000/6300 / 4 threads = 238 + # 1000 * 300/6300 / 1 thread = 48 + $xn.Profile.TimeSpans.TimeSpan.Targets.Target[0].Throughput.InnerText | Should Be 238 + $xn.Profile.TimeSpans.TimeSpan.Targets.Target[1].Throughput.InnerText | Should Be 48 + } + + It "ShouldSetByPipeline" { + { $x | Set-FleetProfile -Throughput 1000 } | Should Not Throw + $xn = $x | Set-FleetProfile -Throughput 1000 + + $xn.Profile.TimeSpans.TimeSpan.Targets.Target[0].Throughput.InnerText | Should Be 238 + $xn.Profile.TimeSpans.TimeSpan.Targets.Target[1].Throughput.InnerText | Should Be 48 + } + + It "ShouldSetUnbounded" { + { $x | Set-FleetProfile -Throughput 0 } | Should Not Throw + $xn = $x | Set-FleetProfile -Throughput 0 + + $xn.Profile.TimeSpans.TimeSpan.ThreadCount | Should be 5 + $xn.Profile.TimeSpans.TimeSpan.RequestCount | Should be 32 + + $xn.Profile.TimeSpans.SelectNodes("TimeSpan/Targets/Target/ThreadsPerFile") | Should BeNullOrEmpty + $xn.Profile.TimeSpans.SelectNodes("TimeSpan/Targets/Target/RequestCount") | Should BeNullOrEmpty + $xn.Profile.Timespans.TimeSpan.Targets.Target[1].InterlockedSequential | Should Be 'true' + } + + It "ShouldSetWarmup" { + $oWarmup | Should Not Be 33 + { $x | Set-FleetProfile -Warmup 33 } | Should Not Throw + $xn = $x | Set-FleetProfile -Warmup 33 + + $xn.Profile.TimeSpans.TimeSpan.Warmup | Should Be 33 + $xn.Profile.TimeSpans.TimeSpan.Duration | Should Be $oDuration + $xn.Profile.TimeSpans.TimeSpan.Cooldown | Should Be $oCooldown + } + + It "ShouldSetDuration" { + $oDuration | Should Not Be 33 + { $x | Set-FleetProfile -Duration 33 } | Should Not Throw + $xn = $x | Set-FleetProfile -Duration 33 + + $xn.Profile.TimeSpans.TimeSpan.Warmup | Should Be $oWarmup + $xn.Profile.TimeSpans.TimeSpan.Duration | Should Be 33 + $xn.Profile.TimeSpans.TimeSpan.Cooldown | Should Be $oCooldown + } + + It "ShouldSetCooldown" { + $oCooldown | Should Not Be 33 + { $x | Set-FleetProfile -Cooldown 33 } | Should Not Throw + $xn = $x | Set-FleetProfile -Cooldown 33 + + $xn.Profile.TimeSpans.TimeSpan.Warmup | Should Be $oWarmup + $xn.Profile.TimeSpans.TimeSpan.Duration | Should Be $oDuration + $xn.Profile.TimeSpans.TimeSpan.Cooldown | Should Be 33 + } +} + +Describe "GetFleetProfileXml" { + It "HasPeak" { + { $peak = Get-FleetProfileXml -Name Peak -BlockSize 8KB -WriteRatio 0 } | Should Not Throw + $peak = Get-FleetProfileXml -Name Peak -BlockSize 8KB -WriteRatio 0 + $peak | Should Not BeNullOrEmpty + } + + It "PeakDoesNotDefineBaseMax" { + $peak = Get-FleetProfileXml -Name Peak -BlockSize 8KB -WriteRatio 0 + $peak.SelectNodes('Profile/TimeSpans/TimeSpan/Targets/Target/BaseFileOffset') | Should BeNullOrEmpty + $peak.SelectNodes('Profile/TimeSpans/TimeSpan/Targets/Target/MaxFileSize') | Should BeNullOrEmpty + } + + It "PeakShouldAcceptBase" { + $peak = Get-FleetProfileXml -Name Peak -BlockSize 8KB -WriteRatio 0 -BaseOffset 1GB + $peak.SelectNodes('Profile/TimeSpans/TimeSpan/Targets/Target/MaxFileSize') | Should BeNullOrEmpty + $n = $peak.SelectNodes('Profile/TimeSpans/TimeSpan/Targets/Target/BaseFileOffset') + $n | Should Not BeNullOrEmpty + $n.Count | Should Be 1 + + $n.Item(0).InnerText | Should Be ([string] 1GB) + } + + It "PeakShouldAcceptMax" { + $peak = Get-FleetProfileXml -Name Peak -BlockSize 8KB -WriteRatio 0 -MaxOffset 2GB + $peak.SelectNodes('Profile/TimeSpans/TimeSpan/Targets/Target/BaseFileOffset') | Should BeNullOrEmpty + $n = $peak.SelectNodes('Profile/TimeSpans/TimeSpan/Targets/Target/MaxFileSize') + $n | Should Not BeNullOrEmpty + + $n.Count | Should Be 1 + $n.Item(0).InnerText | Should Be ([string] 2GB) + } + + It "PeakShouldAcceptBaseMax" { + $peak = Get-FleetProfileXml -Name Peak -BlockSize 8KB -WriteRatio 0 -BaseOffset 1GB -MaxOffset 2GB + + $n = $peak.SelectNodes('Profile/TimeSpans/TimeSpan/Targets/Target/BaseFileOffset') + $n | Should Not BeNullOrEmpty + $n.Count | Should Be 1 + $n.Item(0).InnerText | Should Be ([string] 1GB) + + $n = $peak.SelectNodes('Profile/TimeSpans/TimeSpan/Targets/Target/MaxFileSize') + $n | Should Not BeNullOrEmpty + $n.Count | Should Be 1 + $n.Item(0).InnerText | Should Be ([string] 2GB) + } + + It "PeakShouldAcceptThreads" { + $peak = Get-FleetProfileXml -Name Peak -BlockSize 8KB -WriteRatio 0 -ThreadsPerTarget 2 + + $n = $peak.SelectNodes('Profile/TimeSpans/TimeSpan/Targets/Target/ThreadsPerFile') + $n | Should Not BeNullOrEmpty + $n.Count | Should Be 1 + $n.Item(0).InnerText | Should Be ([string] 2) + } +} + + +InModuleScope VMFleet { + + Describe "GetNextSplit" { + + It "ShouldBeLargest@End" { + $o = [PSCustomObject]@{ + Value = 0 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 5 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 15 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 40 + CutoffType =[CutoffType]::No + } + + $p1, $p2 = GetNextSplit $o 'Value' -OrderBy 'Value' + $p1.Value | Should Be 40 + $p2.Value | Should Be 15 + } + + It "ShouldBeLargest@Begin" { + $o = [PSCustomObject]@{ + Value = 40 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 5 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 15 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 0 + CutoffType =[CutoffType]::No + } + + $p1, $p2 = GetNextSplit $o 'Value' -OrderBy 'Value' + $p1.Value | Should Be 40 + $p2.Value | Should Be 15 + } + + It "ShouldBeFirstOfEqual" { + $o = [PSCustomObject]@{ + Value = 0 + Order = 0 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 10 + Order = 1 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 20 + Order = 2 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 30 + Order = 3 + CutoffType =[CutoffType]::No + } + + $p1, $p2 = GetNextSplit $o 'Value' -OrderBy 'Value' + $p1.Value | Should Be 10 + $p2.Value | Should Be 0 + } + + It "ShouldRespectOrder" { + $o = [PSCustomObject]@{ + Value = 0 + Order = 0 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 10 + Order = 2 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 20 + Order = 3 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 30 + Order = 1 + CutoffType =[CutoffType]::No + } + + $p1, $p2 = GetNextSplit $o 'Value' -OrderBy 'Order' + $p1.Value | Should Be 30 + $p2.Value | Should Be 0 + } + + It "ShouldRespectCutoff" { + $o = [PSCustomObject]@{ + Value = 0 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 10 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 30 + CutoffType =[CutoffType]::Scale + }, + [PSCustomObject]@{ + Value = 60 + CutoffType =[CutoffType]::Scale + } + + $p1, $p2 = GetNextSplit $o 'Value' -OrderBy 'Value' + $p1.Value | Should Be 30 + $p2.Value | Should Be 10 + } + + It "ShouldRespectCutoffOrdered" { + $o = [PSCustomObject]@{ + Value = 0 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 30 + CutoffType =[CutoffType]::Scale + }, + [PSCustomObject]@{ + Value = 10 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 60 + CutoffType =[CutoffType]::Scale + } + + $p1, $p2 = GetNextSplit $o 'Value' -OrderBy 'Value' + $p1.Value | Should Be 30 + $p2.Value | Should Be 10 + } + } + + Describe "GetUpperAnchor" { + + It "ShouldReturnFinalIfNotCutoff" { + $o = [PSCustomObject]@{ + Value = 0 + Order = 0 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 10 + Order = 2 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 20 + Order = 3 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 30 + Order = 1 + CutoffType =[CutoffType]::No + } + + $p1,$p2 = GetUpperAnchor $o -OrderBy 'Order' + $p1.Value | Should Be 20 + $p2.Value | Should Be 10 + } + + It "ShouldReturnFirstCutoff" { + $o = [PSCustomObject]@{ + Value = 0 + Order = 0 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 10 + Order = 2 + CutoffType =[CutoffType]::Scale + }, + [PSCustomObject]@{ + Value = 20 + Order = 3 + CutoffType =[CutoffType]::No + }, + [PSCustomObject]@{ + Value = 30 + Order = 1 + CutoffType =[CutoffType]::No + } + + $p1,$p2 = GetUpperAnchor $o -OrderBy 'Order' + $p1.Value | Should Be 10 + $p2.Value | Should Be 30 + } + } + + Describe "IsProfileThroughputLimited" { + + It "PeakIsNot" { + $x = Get-FleetProfileXml -Name Peak -BlockSize 4KB -WriteRatio 0 + IsProfileThroughputLimited -ProfileXml $x | Should Be $false + } + + It "SqlIs" { + $x = Get-FleetProfileXml -Name Sql + IsProfileThroughputLimited -ProfileXml $x | Should Be $true + } + } + + Describe "IsProfileSingleTimespan" { + + It "PeakIs" { + $x = Get-FleetProfileXml -Name Peak -BlockSize 4KB -WriteRatio 0 + IsProfileSingleTimespan -ProfileXml $x | Should Be $true + } + } + + Describe "IsProfileSingleTarget" { + + It "PeakIs" { + $x = Get-FleetProfileXml -Name Peak -BlockSize 4KB -WriteRatio 0 + IsProfileSingleTarget -ProfileXml $x | Should Be $true + } + + It "SqlIsNot" { + $x = Get-FleetProfileXml -Name Sql + IsProfileSingleTarget -ProfileXml $x | Should Be $false + } + } + + Describe "GetFleetProfileFootprint" { + + BeforeAll { + $sql = Get-FleetProfileXml -Name SQL + $vdi = Get-FleetProfileXml -Name VDI + } + + It "CheckVDI" { + $vdi | Should Not BeNullOrEmpty + $f = GetFleetProfileFootprint -ProfileXml $vdi + $f.Count | Should Be 1 + $f['*1'].BaseOffset | Should Be 0 + $f['*1'].MaxOffset | Should Be (10GB) + } + + It "CheckVDIRead" { + $vdi | Should Not BeNullOrEmpty + $f = GetFleetProfileFootprint -ProfileXml $vdi -Read + $f.Count | Should Be 1 + $f['*1'].BaseOffset | Should Be 0 + $f['*1'].MaxOffset | Should Be (8GB) + } + + It "CheckSQL" { + $sql | Should Not BeNullOrEmpty + $f = GetFleetProfileFootprint -ProfileXml $sql + $f.Count | Should Be 1 + $f['*1'].BaseOffset | Should Be 0 + $f['*1'].MaxOffset | Should Be 0 + } + + It "CheckSQLRead" { + $sql | Should Not BeNullOrEmpty + $f = GetFleetProfileFootprint -ProfileXml $sql -Read + $f.Count | Should Be 1 + $f['*1'].BaseOffset | Should Be (5GB) + $f['*1'].MaxOffset | Should Be 0 + } + } + + Describe "TimespanToString" { + + BeforeAll { + $t0 = [datetime] '7/31/1996 12:00PM' + } + + It "Seconds" { + TimespanToString ($t0.AddMilliseconds(1500) - $t0) | Should Be "01.5s" + } + + It "Minutes" { + TimespanToString ($t0.AddMinutes(15) - $t0) | Should Be "15m:00.0s" + } + + It "Hours" { + TimespanToString ($t0.AddHours(15) - $t0) | Should Be "15h:00m:00.0s" + } + + It "Days" { + TimespanToString ($t0.AddDays(15) - $t0) | Should Be "15d.00h:00m:00.0s" + } + } +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/VMFleet.format.ps1xml b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/VMFleet.format.ps1xml new file mode 100644 index 0000000..2b6393c --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/VMFleet.format.ps1xml @@ -0,0 +1,82 @@ + + + + + + StorageBusBindingTableView + + VolumeEstimate + + + + + + + + + + + + + + + + + + + + + + + + + + + VolumeType + + + Right + + $v = $_.MirrorSize; + $postfixes = @( "B", "KB", "MB", "GB", "TB", "PB" ) + for ($i=0; $v -ge 1024 -and $i -lt $postfixes.Length; $i++) { $v /= 1024; } + return "" + [System.Math]::Round($v,2) + " " + $postfixes[$i]; + + + + MirrorTierName + + + Right + + $v = $_.ParitySize; + $postfixes = @( "B", "KB", "MB", "GB", "TB", "PB" ) + for ($i=0; $v -ge 1024 -and $i -lt $postfixes.Length; $i++) { $v /= 1024; } + return "" + [System.Math]::Round($v,2) + " " + $postfixes[$i]; + + + + ParityTierName + + + Right + + $v = $_.Size; + $postfixes = @( "B", "KB", "MB", "GB", "TB", "PB" ) + for ($i=0; $v -ge 1024 -and $i -lt $postfixes.Length; $i++) { $v /= 1024; } + return "" + [System.Math]::Round($v,2) + " " + $postfixes[$i]; + + + + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/VMFleet.psd1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/VMFleet.psd1 new file mode 100644 index 0000000..f0bb05b --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/VMFleet.psd1 @@ -0,0 +1,197 @@ +<# +VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +# +# Module manifest for module 'VMFleet' +# +# Generated on: 1/30/2021 +# + +@{ + +# Script module or binary module file associated with this manifest. +RootModule = 'VMFleet.psm1' + +# Version number of this module. Even build# is release, odd pre-release (Mj.Mn.Bd.Rv) +ModuleVersion = '2.1.0.0' + +# Supported PSEditions +# CompatiblePSEditions = @() + +# ID used to uniquely identify this module +GUID = '753ad5da-01b3-4cc8-a475-16a09a021384' + +# Author of this module +Author = 'Microsoft Corporation' + +# Company or vendor of this module +CompanyName = 'Microsoft Corporation' + +# Copyright statement for this module +Copyright = '(c) 2021 Microsoft Corporation. All rights reserved.' + +# Description of the functionality provided by this module +Description = 'VM Fleet is a performance characterization and analyst framework for exploring the storage capabilities of Windows Server Hyper-Converged environments with Storage Spaces Direct' + +# Minimum version of the Windows PowerShell engine required by this module +PowerShellVersion = '5.1' + +# Name of the Windows PowerShell host required by this module +# PowerShellHostName = '' + +# Minimum version of the Windows PowerShell host required by this module +# PowerShellHostVersion = '' + +# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# DotNetFrameworkVersion = '' + +# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# CLRVersion = '' + +# Processor architecture (None, X86, Amd64) required by this module +# ProcessorArchitecture = '' + +# Modules that must be imported into the global environment prior to importing this module +# RequiredModules = @() + +# Assemblies that must be loaded prior to importing this module +# RequiredAssemblies = @() + +# Script files (.ps1) that are run in the caller's environment prior to importing this module. +# ScriptsToProcess = @() + +# Type files (.ps1xml) to be loaded when importing this module +# TypesToProcess = @() + +# Format files (.ps1xml) to be loaded when importing this module +FormatsToProcess = @( 'VMFleet.format.ps1xml' ) + +# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess +NestedModules = @( + 'Profile.psm1', + 'WatchCluster.psm1', + 'WatchCPU.psm1' +) + +# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. +FunctionsToExport = @( + 'Clear-FleetPause', + 'Clear-FleetRunState', + 'Convert-FleetXmlToString', + 'Get-FleetComputeTemplate', + 'Get-FleetDisk', + 'Get-FleetDataDiskEstimate', + 'Get-FleetPath', + 'Get-FleetPause', + 'Get-FleetPolynomialFit', + 'Get-FleetPowerScheme', + 'Get-FleetProfileXml', + 'Get-FleetResultLog', + 'Get-FleetVersion', + 'Get-FleetVM', + 'Get-FleetVolumeEstimate', + 'Install-Fleet', + 'Measure-FleetCoreWorkload', + 'Move-Fleet', + 'New-Fleet', + 'Remove-Fleet', + 'Repair-Fleet', + 'Set-ArcConfig', + 'Set-Fleet', + 'Set-FleetPause', + 'Set-FleetPowerScheme', + 'Set-FleetProfile', + 'Set-FleetQoS', + 'Set-FleetRunProfileScript', + 'Show-Fleet', + 'Show-FleetCpuSweep', + 'Show-FleetPause', + 'Start-Fleet', + 'Start-FleetReadCacheWarmup', + 'Start-FleetResultRun', + 'Start-FleetRun', + 'Start-FleetSweep', + 'Start-FleetWriteWarmup', + 'Stop-Fleet', + 'Test-FleetResultRun', + 'Use-FleetPolynomialFit', + 'Watch-FleetCluster', + 'Watch-FleetCPU', + 'Initialize-ArcVMs' +) + +# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. +CmdletsToExport = @( + +) + +# Variables to export from this module +VariablesToExport = $null + +# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. +AliasesToExport = @() + +# DSC resources to export from this module +# DscResourcesToExport = @() + +# List of all modules packaged with this module +# ModuleList = @() + +# List of all files packaged with this module +# FileList = @() + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + ProjectUri = 'https://www.github.com/microsoft/diskspd' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + } # End of PSData hashtable + +} # End of PrivateData hashtable + +# HelpInfo URI of this module +# HelpInfoURI = '' + +# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. +# DefaultCommandPrefix = '' + +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/VMFleet.psm1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/VMFleet.psm1 new file mode 100644 index 0000000..d22a180 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/VMFleet.psm1 @@ -0,0 +1,9294 @@ +<# +VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +Set-StrictMode -Version 3.0 + +# Globals @ Module-only scope (others in CommonFunc) + +enum PauseState { + Current + Stale +} + +enum CutoffType { + No + Scale + CPU + WriteLatency + ReadLatency + Surge +} + +enum RunType { + Default + Baseline + AnchorSearch +} + +enum VolumeType +{ + Mirror + NestedMirror + Parity + MAP + Collect +} + +class VolumeEstimate +{ + [VolumeType] $VolumeType + [uint64] $MirrorSize + [string] $MirrorTierName + [uint64] $ParitySize + [string] $ParityTierName + [uint64] $Size + + VolumeEstimate( + [VolumeType] $VolumeType, + [uint64] $MirrorSize, + [string] $MirrorTierName, + [uint64] $ParitySize, + [string] $ParityTierName + ) + { + $this.VolumeType = $VolumeType + $this.MirrorSize = $MirrorSize + $this.MirrorTierName = $MirrorTierName + $this.ParitySize = $ParitySize + $this.ParityTierName = $ParityTierName + $this.Size = $MirrorSize + $ParitySize + } +} + +$vmSwitchName = 'FleetInternal' +$vmSwitchIP = '169.254.1.1' + +# Note: this can be directly determined by Get-SmbClientConfiguration. Changes are not expected, but +# since the controlling timeout is in the guest lets simply wire it down. Actual is normally 10s, use +# +2s for margin. +$smbInfoCacheLifetime = 12 + +# +# Embedded scripts for VM control/pickup +# +# These are installed in the control directory and referenced/run by the VM Fleet. +# The Set verb is used as a meta-comment. These are never executed in context of the module +# and are not exported. +# + +# +# The reference manual-edit runner script +# +function Set-FleetBaseScript +{ + # buffer size/alighment, threads/target, outstanding/thread, write% + $b = 4; $t = 1; $o = 32; $w = 0 + + # optional - specify rate limit in iops, translated to bytes/ms for DISKSPD + # $iops = 500 + if ($null -ne $iops) { $g = $iops * $b * 1KB / 1000 } + + # io pattern, (r)andom or (s)equential (si as needed for multithread) + $p = 'r' + + # durations of test, cooldown, warmup + $d = 30*60; $cool = 30; $warm = 60 + + $addspec = '' + $gspec = $null + + # cap -> true to capture xml results, otherwise human text + $cap = $false + + ### prior to this is template + + if ($null -ne $g) { $gspec = "g$($g)" } + $result = "result-b$($b)t$($t)o$($o)w$($w)p$($p)$($gspec)" + if ($addspec.Length) { $result += "-$($addspec)" } + $result += "-$(Get-Content C:\vmspec.txt).xml" + $dresult = "l:\result" + $lresultf = Join-Path "C:\run" $result + $dresultf = Join-Path $dresult $result + + if (-not (Get-Item $dresultf -ErrorAction SilentlyContinue)) { + + if ($cap) { + $res = 'xml' + } else { + $res = 'text' + } + + # look for data disk - if present, it will always be disk 1. disk 0 is boot/os. + # only use it if it is a raw device. we do not have a convention for using a + # filesystem on it at this time. + $d1 = Get-Disk -Number 1 -ErrorAction SilentlyContinue + if ($null -ne $d1 -and $d1.IsBoot -eq $false -and $d1.PartitionStyle -eq 'RAW') + { + $target = '#1' + } + else + { + $target = (Get-ChildItem C:\run\testfile?.dat) + } + + $gspec = $null + if ($null -ne $g) { $gspec = "-g$($g)" } + $o = C:\run\diskspd.exe -Z20M -z -h `-t$t `-o$o $gspec `-b$($b)k `-$($p)$($b)k `-w$w `-W$warm `-C$cool `-d$($d) -D -L `-R$res $target + + if ($cap) { + + # export result and indicate done flag to control + + $o | Out-File $lresultf -Encoding ascii -Width ([int32]::MaxValue) + [PSCustomObject]@{ + Result = @( $lresultf ) + Stash = $null + } + + } else { + + #emit to human watcher + $o | Out-Host + } + + } else { + + Write-Host -fore green already done $dresultf + + # indicate done flag to control + # this should only occur if controller does not change variation + [PSCustomObject]@{ + Result = $null + Stash = $null + } + } + + [system.gc]::Collect() +} + +# +# The VM controller script, run by the VMs on start/login +# +function Set-FleetControlScript +{ + param( + [string] $connectuser, + [string] $connectpass + ) + + function Get-ProcessDump + { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.Diagnostics.Process] + $Process, + + [Parameter(Mandatory = $true)] + [string] + $DumpFile + ) + + begin + { + $wer = [PSObject].Assembly.GetType('System.Management.Automation.WindowsErrorReporting') + $methods = $wer.GetNestedType('NativeMethods', 'NonPublic') + $flags = [Reflection.BindingFlags] 'NonPublic, Static' + $api = $methods.GetMethod('MiniDumpWriteDump', $Flags) + + # Normalize unqualified paths to current working directory + $dfp = Split-Path $DumpFile -Parent + if ($dfp.Length -eq 0) + { + $DumpFile = Join-Path $PWD.Path $DumpFile + } + } + + process + { + # Subst in PID to uniquify multiple processes / same name + $dfs = $DumpFile -split '\.' + $dfs[-2] += "-$($Process.ID)" + $df = $dfs -join '.' + + $f = New-Object IO.FileStream($df, [IO.FileMode]::Create) + + $r = $api.Invoke($null, @($Process.Handle, + $Process.Id, + $f.SafeFileHandle, + [UInt32] 2, # FullMemory + [IntPtr]::Zero, + [IntPtr]::Zero, + [IntPtr]::Zero)) + + $f.Close() + + if (-not $r) + { + $errorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() + + # Remove any partially written dump file. + Remove-Item $df -Force -ErrorAction SilentlyContinue + + throw "failed to dump $($Process.Name) PID $($Process.Id), error $errorcode" + } + else + { + Get-ChildItem $df + } + } + } + + function CopyKeyIf + { + param( + [Parameter(Mandatory = $true, Position = 0)] + [hashtable] + $HSource, + + [Parameter(Mandatory = $true, Position = 1)] + [hashtable] + $HDest, + + [Parameter(Mandatory = $true, Position = 2)] + [string] + $Key + ) + + if ($HSource.ContainsKey($Key)) + { + $HDest[$Key] = $HSource[$Key] + } + } + + function LogOutput + { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true, Position = 0, ValueFromRemainingArguments)] + [ValidateNotNullOrEmpty()] + [string[]] + $Message, + + [Parameter()] + [System.ConsoleColor] + $ForegroundColor, + + [Parameter()] + [switch] + $IsVb, + + [Parameter()] + [switch] + $NoNewline, + + [Parameter()] + [switch] + $CarriageReturn + ) + + $m = ((Get-Date).GetDateTimeFormats('s')[0] + ": $($Message -join ' ')") + if ($PSBoundParameters.ContainsKey('IsVb')) + { + Write-Verbose $m + } + else + { + $whParam = @{} + CopyKeyIf $PSBoundParameters $whParam 'ForegroundColor' + CopyKeyIf $PSBoundParameters $whParam 'NoNewline' + if ($PSBoundParameters.ContainsKey('CarriageReturn')) + { + $m += "`r" + } + Write-Host @whParam $m + } + } + + function DisableWindowsRE + { + # + # Disable the Windows Recovery console. Under aggresive conditions like repeated start/stop on the part + # of a runner, Windows RE may get triggered and trap the VMs in the recovery console. While recoverable + # with Repair-Fleet, lets prevent that since RE doesn't provide value to VM Fleet ops. + # + # It would be nice if we could do this during VM provisioning. + # + + $state = reagentc /info |% { + + if ( $_ -match 'Windows RE status:\s+(\S+)') + { + $matches[1] + } + } + + if ($state -eq 'Enabled') + { + $out = reagentc /disable + $status = $? + LogOutput "Disabling Windows RE: $out" + if (-not $status) + { + LogOutput -ForegroundColor Red "Failed to disable Windows RE - VM may enter the recovery console" + } + } + } + + function ValidateSmbMapping + { + param( + [Parameter(Mandatory = $true)] + $LocalDriveLetter, + + [Parameter(Mandatory = $true)] + $RemotePath, + + [Parameter()] + $UserName, + + [Parameter()] + $Password, + + [Parameter()] + $TestPath + ) + + $maxAttempt = 10 + $localPath = "$($LocalDriveLetter):" + if ($PSBoundParameters.ContainsKey('TestPath')) + { + # Cannot Join-Path since it will attempt to resolve: this may not exist at time zero + $testPath = $localPath + '\' + $TestPath + } + else + { + $testPath = $localPath + } + + $mapParams = @{ + LocalPath = $localPath + RemotePath = $RemotePath + } + CopyKeyIf $PSBoundParameters $mapParams 'UserName' + CopyKeyIf $PSBoundParameters $mapParams 'Password' + + function Create + { + LogOutput "CREATE SMB Mapping $localPath => $remotePath" + $null = New-SmbMapping @mapParams + } + + function Teardown + { + LogOutput "REMOVE SMB Mapping $localPath => $remotePath" + Remove-SmbMapping -LocalPath $localPath -Force -Confirm:$false -UpdateProfile + } + + for ($nAttempt = 0; $true; $nAttempt += 1) + { + # Indicate connection rebuild attempts (do not emit on first pass - successful validation s.b. silent) + if ($nAttempt -gt 0) + { + LogOutput "VALIDATE SMB Mapping $localPath => $remotePath (attempt $nAttempt)" + } + + # Throw out after maxAttempts + if ($nAttempt -ge $maxAttempt) + { + LogOutput -ForegroundColor Red "FAIL: SMB connection not recoverable after $maxAttempt tries" + throw "SMB Connection Failure" + } + + $mapping = Get-SmbMapping -LocalPath $localPath -ErrorAction SilentlyContinue + + # Create missing mapping + if ($null -eq $mapping) + { + LogOutput "NOT PRESENT SMB Mapping @ $localPath" + Start-Sleep 1 + Create + continue + } + + # Mapping reports bad, teardown and recreate + if ($mapping.Status -ne 'OK') + { + LogOutput "NOT OK SMB Mapping @ $localPath reporting status = $($mapping.Status)" + Start-Sleep 1 + Teardown + Create + continue + } + + # If test path inaccessible, try teardown/recreate to recover + if (-not (Test-Path -Path $testPath -ErrorAction Continue)) + { + LogOutput "ACCESS FAILED to test path @ $testPath" + Start-Sleep 1 + Teardown + Create + continue + } + + # Indicate connection rebuilt successfully (do not emit on successful validation of existing) + if ($nAttempt -gt 0) + { + LogOutput -ForegroundColor Green "SMB Mapping $localPath => $remotePath connected" + } + + break + } + } + + # Drive mapping for initial/periodic validation + $mapParam = @{ + LocalDriveLetter = 'L' + RemotePath = '\\169.254.1.1\c$\clusterstorage\collect' + TestPath = 'flag\pause' + UserName = $connectuser + Password = $connectpass + } + + # Initial configuration checks + ValidateSmbMapping @mapParam + DisableWindowsRE + + # update tooling + Copy-Item L:\tools\* C:\run -Force + + $run = 'C:\run\run.ps1' + $control = 'C:\run\control.ps1' + $stashDir = "C:\run\stash" + + $null = mkdir $stashDir -ErrorAction SilentlyContinue + + $myname = Get-Content C:\vmspec.txt + $mypause = "L:\flag\pause-$myname" + $mydone = "L:\flag\done-$myname" + + $pause = $false + $gowait = $false + $gowaitf = $false + + function get-newfile { + param( + [string] + $Source, + + [string] + $Dest, + + [string[]] + $Exclude, + + [switch] + $Silent + ) + + $gcParam = @{ Path = $Source } + if ($PSBoundParameters.ContainsKey('Exclude')) + { + $gcParam['Exclude'] = $Exclude + } + $sf = Get-ChildItem @gcParam | Sort-Object -Property LastWriteTime -Descending | Select-Object -first 1 + $df = Get-Item $Dest -ErrorAction SilentlyContinue + + # no source? + if ($null -eq $sf) + { + LogOutput -ForegroundColor Green NO source present + return $null + } + + # no destination, always take source + if ($null -eq $df) + { + return $sf + } + + if ($sf.LastWriteTime -gt $df.LastWriteTime) + { + $sfh = Get-FileHash $sf + $dfh = Get-FileHash $df + + # files are different, indicate + if ($sfh.Hash -ne $dfh.Hash) + { + return $sf + } + + # files are the same but the last write time has moved - ignore, pull + # up lastwrite on destination + + LogOutput -ForegroundColor Green IGNORE newer last write with no source change + $df.LastWriteTime = $sf.LastWriteTime + } + + # Have latest, indicate no new + return $null + } + + function get-flagfile( + [ValidateSet('Go','Pause','Done')] + [string] $Flag + ) + { + switch ($Flag) + { + 'Go' { $f = 'L:\flag\go' } + 'Pause' { $f = 'L:\flag\pause' } + 'Done' { $f = "L:\flag\done-$myname" } + } + + if (Test-Path $f) { + [int](Get-Content $f -ErrorAction SilentlyContinue) + } else { + 0 + } + } + + # Active (responded to) and Current (content) of respective flag + $flag = @{} + foreach ($f in 'pause','go') + { + $flag.$f = [pscustomobject] @{ acked = 0; current = 0 } + } + + # Get acked go flag at time zero; ticks forward during operation + $flag.go.acked = get-flagfile Done + + while ($true) { + + ValidateSmbMapping @mapParam + + # update control? + # this passes back out to the launcher which re-executes the controller + $newf = get-newfile -Source L:\control\control.ps1 -Dest $control -Silent + if ($null -ne $newf) { + LogOutput "New $($newf.Name) @ $($newf.LastWriteTime)" + Copy-Item $newf -Destination $control -Force + break + } + + try + { + # check go epoch - this can change while paused (say, clear) + $flag.go.current = get-flagfile Go + + # outside go run, drop ack + if ($flag.go.current -eq 0 -and $flag.go.acked -ne 0) + { + LogOutput "CLEAR Go ENTER FREE RUN" + Write-Output 0 > $mydone + $flag.go.acked = 0 + } + + # check and acknowledge pause - only drop flag once + $flag.pause.current = get-flagfile Pause + if ($flag.pause.current -ne 0) { + + # drop into pause flagfile if needed + if ($pause -eq $false -or ($flag.pause.acked -ne $flag.pause.current)) { + + LogOutput -ForegroundColor Red "PAUSED (flag @ $($flag.pause.current))" + Write-Output $flag.pause.current > $mypause + $flag.pause.acked = $flag.pause.current + + $pause = $true + } + + continue + } + + # pause is now clear + $pause = $false + if ($flag.pause.acked -ne 0) + { + LogOutput -ForegroundColor Red "PAUSE RELEASED (flag @ $($flag.pause.acked))" + $flag.pause.acked = 0 + + # Reissue go wait logging as appropriate + $gowait = $gowaitf = $false + } + + # if go is zero this is free-running new/existing run file + # if go is nonzero but we have already acked, we are waiting for a new go + if ($flag.go.current -ne 0) + { + if ($flag.go.current -eq $flag.go.acked) + { + if ($gowait -eq $false) + { + $gowait = $true + LogOutput -ForegroundColor Cyan "WAITING FOR GO" + } + + continue + } + } + + # check for update to run script + $newf = get-newfile -Source L:\control\*.ps1 -Dest $run -Exclude control.ps1 + $runf = Get-Item $run -ErrorAction SilentlyContinue + + if ($null -eq $runf -and $null -eq $newf) { + LogOutput -ForegroundColor Yellow "NO run file local or from fleet" + continue + } + + # if go is nonzero, only move on new run file + if ($flag.go.current -ne 0) + { + if ($null -eq $newf) + { + if ($gowaitf -eq $false) + { + $gowaitf = $true + LogOutput -ForegroundColor Cyan "WAITING on run file for GO $($flag.go.current)" + } + + continue + } + } + + # GO check complete, lift squelches + $gowait = $gowaitf = $false + + # copy new file if available + if ($null -ne $newf) + { + Copy-Item $newf -Destination $run -Force + $runf = Get-Item $run + $msg = "NEW $($newf.Name) =>" + } + else + { + $msg = "EXISTING" + } + LogOutput -ForegroundColor Cyan "$msg $($runf.Name) $($runf.lastwritetime)" + + # log start (free/GO) + if ($flag.go.current -ne 0) + { + $msg = "GO $($flag.go.current)" + } + else + { + $msg = "FREE RUN" + } + LogOutput -ForegroundColor Cyan "STARTING $msg" + LogOutput -ForegroundColor Cyan "BEGIN" ("*"*20) + + # If data disk is present, bring it online/readwrite if needed (initial state). + # Note that only a single disk is supported at this time. + # This is delayed so that hot-resize/attach is possible without restarting the VM. + $d1 = Get-Disk -Number 1 -ErrorAction SilentlyContinue + if ($null -ne $d1) + { + if ($d1.IsOffline) { $d1 | Set-Disk -IsOffline:$false } + if ($d1.IsReadOnly) { $d1 | Set-Disk -IsReadOnly:$false } + } + + # launch and monitor pause and new run file + $j = start-job -arg $run { param($run) & $run } + while ($null -eq ($jf = wait-job $j -Timeout 1)) + { + $halt = $null + + # check pause or new run file: if so, stop and loop + if (0 -ne (get-flagfile Pause)) + { + $halt = 'pause set' + + # snap diskspd process dumps in case this pause is due to forced + # termination as a result of non-responsive / slow result return + Remove-Item c:\run\diskspd*.dmp -ErrorAction SilentlyContinue -Force + Get-Process diskspd | Get-ProcessDump -DumpFile c:\run\diskspd.dmp |% { LogOutput "DUMP: $($_.FullName)" } + } + # unexpected go change? + elseif ($flag.go.current -ne (get-flagfile Go)) + { + $halt = 'go change' + } + # normal edit/drop loop (non-go/done control) - new run file + elseif (get-newfile -Source L:\control\*.ps1 -Dest $run -Exclude control.ps1 -Silent) + { + $halt = 'new run file' + } + # new controller dropped + elseif (get-newfile -Source L:\control\control.ps1 -Dest $control -Silent) + { + $halt = 'new controller' + } + + if ($null -ne $halt) + { + LogOutput -ForegroundColor Yellow "STOPPING (reason: $halt)" + $j | stop-job + $j | remove-job + + # halts end any outstanding GO run + if ($flag.go.current -ne 0) + { + LogOutput -ForegroundColor Yellow "HALT GO $($flag.go.current)" + Write-Output $flag.go.current > $mydone + $flag.go.acked = $flag.go.current + } + break + } + } + + # job finished? + if ($null -ne $jf) { + $result = $jf | receive-job + + if ($null -ne $result) + { + # log free run completion + if ($flag.go.current -eq 0) + { + LogOutput -ForegroundColor Yellow "DONE CURRENT" + } + # log/ack GO completion + else + { + LogOutput -ForegroundColor Yellow "DONE GO $($flag.go.current)" + Write-Output $flag.go.current > $mydone + } + $flag.go.acked = $flag.go.current + + # clear prior stash, if any + Get-ChildItem $stashDir | Remove-Item -Recurse -Force + + # validate SMB connection before pushing results out + ValidateSmbMapping @mapParam + + # propagate any results, stashing them locally along + # with any other content requested (triage material) + foreach ($f in $result.Result) + { + $null = xcopy /j /y /q $f "l:\result" + $null = xcopy /j /y /q $f $stashDir + Remove-Item $f -Force + } + + foreach ($f in $result.Stash) + { + $null = xcopy /j /y /q $f $stashDir + Remove-Item $f -Force + } + } + + $jf | remove-job + } + + LogOutput -ForegroundColor Cyan "END" ("*"*20) + } + catch + { + $_ + } + finally + { + # force gc to teardown potentially conflicting handles and enforce min pause + [system.gc]::Collect() + Start-Sleep 1 + } + } +} + +function Set-SweepTemplateScript +{ + # __Unique__ + # buffer size/alighment, threads/target, outstanding/thread, write% + $b = __b__; $t = __t__; $o = __o__; $w = __w__ + + # optional - specify rate limit in iops _-gNNNi + $iops = __iops__ + + # io pattern, (r)andom or (s)equential (si as needed for multithread) + $p = '__p__' + + # durations of test, cooldown, warmup + $d = __d__; $cool = __Cool__; $warm = __Warm__ + + # handle autoscaled thread count (1 per LP/VCPU) + if ($t -eq 0) + { + $t = (Get-CimInstance Win32_Processor | Measure-Object -Property NumberOfLogicalProcessors -Sum).Sum + } + + # sweep template always captures + $addspec = '-__AddSpec__' + if ($addspec.Length -eq 1) { $addspec = $null } + $iopspec = $null + if ($null -ne $iops) { $iopspec = "iops$($iops)" } + $result = "result-b$($b)t$($t)o$($o)w$($w)p$($p)$($iopspec)$($addspec)-$(Get-Content C:\vmspec.txt).xml" + $dresult = "L:\result" + $lresultf = Join-Path "C:\run" $result + $dresultf = Join-Path $dresult $result + + ### prior to this is template + + if (-not (Get-Item $dresultf -ErrorAction SilentlyContinue)) { + + # look for data disk - if present, it will always be disk 1. disk 0 is boot/os. + # only use it if it is a raw device. we do not have a convention for using a + # filesystem on it at this time. + $d1 = Get-Disk -Number 1 -ErrorAction SilentlyContinue + if ($null -ne $d1 -and $d1.IsBoot -eq $false -and $d1.PartitionStyle -eq 'RAW') + { + $target = '#1' + } + else + { + $target = (Get-ChildItem C:\run\testfile?.dat) + } + + $res = 'xml' + $gspec = $null + if ($null -ne $iops) { $gspec = "-g$($iops)i" } + + C:\run\diskspd.exe -Z20M -z -h `-t$t `-o$o $gspec `-b$($b)k `-$($p)$($b)k `-w$w `-W$warm `-C$cool `-d$($d) -D -L `-R$res $target > $lresultf + + [PSCustomObject]@{ + Result = @( $lresultf ) + Stash = $null + } + + } else { + + write-host -fore green already done $dresultf + + # indicate done flag to control + # this should only occur if control does not change variation + [PSCustomObject]@{ + Result = $null + Stash = $null + } + } + + [system.gc]::Collect() +} + +function Set-RunProfileTemplateScript +{ + # __Unique__ + $addspec = '__AddSpec__' + $result = "result" + if ($addspec.Length) { $result += "-$($addspec)" } + $result += "-$(Get-Content C:\vmspec.txt).xml" + $dresult = "L:\result" + $lresultf = Join-Path "C:\run" $result + $dresultf = Join-Path $dresult $result + + $prof = '__Profile__' + $lprof = "C:\run\profile.xml" + + if (-not (Get-Item $dresultf -ErrorAction SilentlyContinue)) { + + # look for data disk - if present, it will always be disk 1. disk 0 is boot/os. + # only use it if it is a raw device. we do not have a convention for using a + # filesystem on it at this time. + $d1 = Get-Disk -Number 1 -ErrorAction SilentlyContinue + if ($null -ne $d1 -and $d1.IsBoot -eq $false -and $d1.PartitionStyle -eq 'RAW') + { + $target = '#1' + } + else + { + $target = (Get-ChildItem C:\run\testfile?.dat) + } + + Copy-Item $prof $lprof + C:\run\diskspd.exe -z `-X$($lprof) $target > $lresultf + + # stash profile for triage and return payload for export + [PSCustomObject]@{ + Result = @( $lresultf ) + Stash = @( $lprof ) + } + } else { + + write-host -fore green already done $dresultf + + # indicate done flag to control + # this should only occur if control does not change variation + [PSCustomObject]@{ + Result = $null + Stash = $null + } + } + + [system.gc]::Collect() +} + +################### + +# Common functions for local/remote sessions + +$CommonFunc = { + + # + # Globals + # + + enum PathType { + + # Paths + Collect + Control + Result + Tools + Flag + + # Files + Pause + Go + Done + } + + $collectVolumeName = 'collect' + + function CreateErrorRecord + { + [CmdletBinding()] + param + ( + [String] + $ErrorId, + + [String] + $ErrorMessage, + + [System.Management.Automation.ErrorCategory] + $ErrorCategory, + + [Exception] + $Exception, + + [Object] + $TargetObject + ) + + if($null -eq $Exception) + { + $Exception = New-Object System.Management.Automation.RuntimeException $ErrorMessage + } + + $errorRecord = New-Object System.Management.Automation.ErrorRecord @($Exception, $ErrorId, $ErrorCategory, $TargetObject) + return $errorRecord + } + + # Copy key/value from Source->Destination hash if present + function CopyKeyIf + { + param( + [Parameter(Mandatory = $true, Position = 0)] + [hashtable] + $HSource, + + [Parameter(Mandatory = $true, Position = 1)] + [hashtable] + $HDest, + + [Parameter(Mandatory = $true, Position = 2)] + [string] + $Key + ) + + if ($HSource.ContainsKey($Key)) + { + $HDest[$Key] = $HSource[$Key] + } + } + + function LogOutput + { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true, Position = 0, ValueFromRemainingArguments)] + [ValidateNotNullOrEmpty()] + [string[]] + $Message, + + [Parameter()] + [System.ConsoleColor] + $ForegroundColor, + + [Parameter()] + [switch] + $IsVb, + + [Parameter()] + [switch] + $NoNewline, + + [Parameter()] + [switch] + $CarriageReturn + ) + + $m = ((Get-Date).GetDateTimeFormats('s')[0] + ": $($Message -join ' ')") + if ($PSBoundParameters.ContainsKey('IsVb')) + { + Write-Verbose $m + } + else + { + $whParam = @{} + CopyKeyIf $PSBoundParameters $whParam 'ForegroundColor' + CopyKeyIf $PSBoundParameters $whParam 'NoNewline' + if ($PSBoundParameters.ContainsKey('CarriageReturn')) + { + $m += "`r" + } + Write-Host @whParam $m + } + } + + # + # Convert an absolute local path to the equivalent remote path via SMB admin shares + # ex: C:\foo\bar & scratch -> \\scratch\C$\foo\bar + # + + function Get-AdminSharePathFromLocal + { + [CmdletBinding()] + param( + [Parameter()] + [string] + $Node, + + [Parameter()] + [string] + $LocalPath + ) + + "\\"+$Node+"\"+$LocalPath[0]+"$\"+$LocalPath.Substring(3,$LocalPath.Length-3) + } + function Stop-After($step) + { + if ($stopafter -eq $step) { + Write-Host -ForegroundColor Green Stop after $step + return $true + } + return $false + } + + function Get-AccessNode + { + [CmdletBinding()] + param ( + [Parameter()] + [string] + $Cluster = '.' + ) + + $nodes = @(Get-ClusterNode @PsBoundParameters |? State -eq Up) + if ($nodes.Length -eq 0) + { + throw "No nodes of cluster $Cluster are accessible at this time" + } + + $nodes[0].Name + } + + # + # This helper function deals with stable identification of the CSV corresponding + # to a given virtual disk. + # + # In system restore case where a cluster is brought up on previously configured storage + # the CSV name will not restate the virtualdisk name. By looking at the volume device path + # seen by CSV and the volume stack we can make the association. At the volume layer the + # filesystem label will (should) reliably give us the name mapping - and is defensive against + # potentially renamed CSV mounts. + # + # The name is attached to the CSV object as the VDName property. + # + function GetMappedCSV + { + [CmdletBinding()] + param( + [Parameter()] + [string] + $Cluster = '.' + ) + + $node = Get-AccessNode @PsBoundParameters + $csv = Get-ClusterSharedVolume @PsBoundParameters + + # Map of volume path (ex: \?\Volume{9759283a-54c7-40b3-b07d-faef008e1a8d}\) to volume object + $vh = @{} + Get-Volume -CimSession $node |? FileSystem -eq CSVFS |% { $vh[$_.Path] = $_ } + + $csv |% { + + # If the CSV maps to a visible volume, attach the filesystemlabel of the respective volume + # to it. Its volume path us mentioned here under the CSV object. + $v = $vh[$_.SharedVolumeInfo.Partition.Name] + + if ($null -ne $v) + { + $VDName = $v.FileSystemLabel + } + else + { + $VDName = $null + } + + # Add and emit + $_ | Add-Member -NotePropertyName VDName -NotePropertyValue $VDName -PassThru + } + } + + function Get-FleetPath + { + [CmdletBinding()] + param ( + [Parameter()] + [string] + $Cluster = ".", + + [Parameter()] + [PathType[]] + $PathType, + + [Parameter()] + [switch] + $Local + ) + + $c = Get-Cluster -Name $Cluster + if ($null -eq $c) + { + return $null + } + + $accessNode = $null + if (-not $Local -and $PSBoundParameters.ContainsKey("Cluster")) + { + $accessNode = Get-AccessNode -Cluster $Cluster + } + + $clusterStorage = $c.SharedVolumesRoot + $collectPath = Join-Path $clusterStorage $collectVolumeName + + foreach ($type in $PathType) + { + $path = switch ($type) + { + ([PathType]::Collect) { $collectPath } + ([PathType]::Control) { Join-Path $collectPath "control" } + + # Note: these paths should move up to collect at v1.0 (don't bury them in control) + + ([PathType]::Result) { Join-Path $collectPath "result" } + ([PathType]::Tools) { Join-Path $collectPath "tools" } + ([PathType]::Flag) { Join-Path $collectPath "flag" } + + ([PathType]::Done) { Join-Path $collectPath "flag\done" } + ([PathType]::Go) { Join-Path $collectPath "flag\go" } + ([PathType]::Pause) { Join-Path $collectPath "flag\pause" } + } + + if ($null -ne $accessNode) + { + $path = Get-AdminSharePathFromLocal -Node $accessNode $path + } + + $path + } + } + + function GetLogmanLocal + { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string] + $Name + ) + + $raw = logman query $Name + if ($?) + { + $inctrs = $false + $ctrs = @() + $props = @{} + + $raw |% { + if ($inctrs) + { + # Trim leading spaces. Empty terminates the block. + $ctr = $_ -replace '^\s+','' + if ($ctr.Length -eq 0) + { + $inctrs = $false + } + else + { + $ctrs += $ctr + } + } + elseif ($_ -match '\S+\:\s+\S+') + { + $kv = $_ -split ':\s+',2 + # Basic k: v property + $props[$kv[0] -replace '\s',''] = $kv[1] + } + elseif ($_ -eq 'Counters:') + { + # Begin consuming counter names + $inctrs = $true + } + } + + $props['Counters'] = $ctrs + [pscustomobject] $props + } + } + + # Initializes Archci configs and retrieves extended location required for resource creation using Azure CLI + function InitializeAndGetArcHCIExtendedLoc($azUser, $azPassword, $resourceGroup) { + az config set extension.use_dynamic_install=yes_without_prompt core.encrypt_token_cache=false core.disable_confirm_prompt=true core.only_show_errors=true; + $subscription = ((Get-AzureStackHCI | select AzureResourceUri).AzureResourceUri).Split("/")[2] + $location = (Get-AzureStackHCI | select Region).Region + $isLoggedIn = az login -u $azUser -p $azPassword + if($isLoggedIn -eq $null){ + throw "Azure login failed with error - $isLoggedIn" + } + az account set -s $subscription + try{ + $groupResult = az group show --name $resourceGroup | ConvertFrom-Json + if ($groupResult.properties.provisioningState -eq "Succeeded") { + LogOutput "Resource group $resourceGroup already exists" + } + else { + LogOutput "Creating new Resource group $resourceGroup ..." + az group create --name $resourceGroup --location $location + } + $rbResourceGroup = ((Get-AzureStackHCI | select AzureResourceUri).AzureResourceUri).Split("/")[4] + $customLocationResult = az customlocation list --resource-group $rbResourceGroup | ConvertFrom-Json + $extendedLocation = $customLocationResult[0].id # by default uses the first available custom location + } + catch{ + throw $_.Exception.Message + } + return $extendedLocation + } + + function ApplySpecialization( $path, $vmspec, $admin, $adminPass, $connectUser, $connectPass ) { + # all steps here can fail immediately without cleanup + + # error accumulator + $ok = $true + + # create run directory + + Remove-Item -Recurse -Force z:\run -ErrorAction SilentlyContinue + mkdir z:\run + $ok = $ok -band $? + if (-not $ok) { + Write-Error "failed run directory creation for $vhdpath" + return $ok + } + + # autologon + $null = reg load 'HKLM\tmp' z:\windows\system32\config\software + $ok = $ok -band $? + $null = reg add 'HKLM\tmp\Microsoft\Windows NT\CurrentVersion\WinLogon' /f /v DefaultUserName /t REG_SZ /d $admin + $ok = $ok -band $? + $null = reg add 'HKLM\tmp\Microsoft\Windows NT\CurrentVersion\WinLogon' /f /v DefaultPassword /t REG_SZ /d $adminPass + $ok = $ok -band $? + $null = reg add 'HKLM\tmp\Microsoft\Windows NT\CurrentVersion\WinLogon' /f /v AutoAdminLogon /t REG_DWORD /d 1 + $ok = $ok -band $? + $null = reg add 'HKLM\tmp\Microsoft\Windows NT\CurrentVersion\WinLogon' /f /v Shell /t REG_SZ /d 'powershell.exe -noexit -command C:\users\administrator\launch.ps1' + $ok = $ok -band $? + + $null = reg unload 'HKLM\tmp' + $ok = $ok -band $? + if (-not $ok) { + Write-Error "failed autologon injection for $vhdpath" + return $ok + } + + # scripts + + Copy-Item -Force C:\ClusterStorage\collect\control\control.ps1 z:\run\control.ps1 + $ok = $ok -band $? + if (-not $ok) { + Write-Error "failed injection of specd master.ps1 for $vhdpath" + return $ok + } + + # + # Wrapper for the controller script to auto-restart on failure/update. + # This is the autologin script itself. + # + function Set-FleetLauncherScript { + $script = 'C:\run\control.ps1' + + while ($true) { + Write-Host -fore Green Launching $script `@ $(Get-Date) + try { & $script -connectuser __CONNECTUSER__ -connectpass '__CONNECTPASS__' } catch {} + Start-Sleep -Seconds 1 + } + } + + Remove-Item -Force z:\users\administrator\launch.ps1 -ErrorAction SilentlyContinue + (Get-Command Set-FleetLauncherScript).ScriptBlock | % { + $_ -replace '__CONNECTUSER__',$connectUser -replace '__CONNECTPASS__',$connectPass + } > z:\users\administrator\launch.ps1 + $ok = $ok -band $? + if (-not $ok) { + Write-Error "failed injection of launch.ps1 for $vhdpath" + return $ok + } + + Write-Output $vmspec > z:\vmspec.txt + $ok = $ok -band $? + if (-not $ok) { + Write-Error "failed injection of vmspec for $vhdpath" + return $ok + } + + # load files + $f = 'z:\run\testfile1.dat' + if (-not (Get-Item $f -ErrorAction SilentlyContinue)) { + fsutil file createnew $f (10GB) + $ok = $ok -band $? + fsutil file setvaliddata $f (10GB) + $ok = $ok -band $? + } + if (-not $ok) { + Write-Error "failed creation of initial load file for $vhdpath" + return $ok + } + + return $ok + } + + function SpecializeVhd { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)][string]$vhdpath, + [Parameter(Mandatory = $true)][string]$admin, + [Parameter(Mandatory = $true)][string]$adminPass, + [Parameter(Mandatory = $true)][string]$connectUser, + [Parameter(Mandatory = $true)][string]$connectPass, + [Parameter(Mandatory = $false)][string]$name + ) + $ok = $true + $vhd = (Get-Item $vhdpath) + if ($name) { + $vmspec = $name + } + else { + $vmspec = $vhd.BaseName + } + + + # mount vhd and its largest partition + $o = Mount-VHD $vhd -NoDriveLetter -Passthru + if ($null -eq $o) { + Write-Error "failed mount for $vhdpath" + return $false + } + $p = Get-Disk -number $o.DiskNumber | Get-Partition | Sort-Object -Property size -Descending | Select-Object -first 1 + $p | Add-PartitionAccessPath -AccessPath Z: + $ok = ApplySpecialization Z: $vmspec $admin $adminPass $connectUser $connectPass + + Remove-PartitionAccessPath -AccessPath Z: -InputObject $p + Dismount-VHD -DiskNumber $o.DiskNumber + + return $ok + } +} + +# Evaluate common block into local session +. $CommonFunc + +# Utility functions only for the local session + +function TimespanToString +{ + param( + [timespan] $TimeSpan + ) + + # Autoranging output + if ($TimeSpan.TotalDays -ge 1) + { + $TimeSpan.ToString("dd\d\.hh\h\:mm\m\:ss\.f\s") + } + elseif ($TimeSpan.TotalHours -ge 1) + { + $TimeSpan.ToString("hh\h\:mm\m\:ss\.f\s") + } + elseif ($TimeSpan.TotalMinutes -ge 1) + { + $TimeSpan.ToString("mm\m\:ss\.f\s") + } + else + { + $TimeSpan.ToString("ss\.f\s") + } +} + +# Produce a good-enough parse of logman collector status +function GetLogman +{ + [CmdletBinding()] + param( + [Parameter( + Mandatory = $true + )] + [string[]] + $Computer, + + [Parameter()] + [string] + $Name = "" + ) + + $n = "perfctr" + if ($Name.Length) { $n += "-$Name" } + + Invoke-CommonCommand -ComputerName $Computer -InitBlock $CommonFunc -ScriptBlock { + + GetLogmanLocal -Name $using:n + } +} + +# Start/restart perf collection on given computer(s)/counter set with named blg +function StartLogman +{ + [CmdletBinding()] + param( + [Parameter( + Mandatory = $true + )] + [string[]] + $Computer, + + [Parameter()] + [string] + $Name = "", + + [Parameter( + Mandatory = $true + )] + [string[]] + $Counters, + + [Parameter()] + [ValidateRange(1,60)] + [int] + $SampleInterval = 1 + ) + + $n = "perfctr" + if ($Name.Length) { $n += "-$Name" } + + Invoke-CommonCommand -ComputerName $Computer -InitBlock $CommonFunc -ScriptBlock { + + # clean up any pre-existing collector & corresponding blg due to failed teardown/et.al. + $existing = GetLogmanLocal -Name $using:n + if ($null -ne $existing) + { + Write-Verbose "logman stop/delete pre-existing $($using:n)" + if ($existing.Status -eq 'Running') + { + $null = logman stop $using:n + } + $null = logman delete $using:n + if (Test-Path $existing.OutputLocation) + { + Remove-Item $existing.OutputLocation -Force + } + } + + $f = Join-Path $env:TEMP "$($using:n)-$($env:COMPUTERNAME).blg" + + # clean up any stale pre-existing blg + if (Test-Path $f) { Remove-Item $f -Force } + + $o = logman create counter $using:n -o $f -f bin -si $using:SampleInterval --v -c $using:Counters 2>&1 + $s = $? + $m = "logman create @ $env:COMPUTERNAME : $o" + if (-not $s) { Write-Error $m } else { Write-Verbose $m } + + $o = logman start $using:n + $s = $? + $m = "logman start @ $env:COMPUTERNAME : $o" + if (-not $s) { Write-Error $m } else { Write-Verbose $m } + } +} + +# Stop perf collection on given computer(s)/counter set with named blg +# Note that copyout is remoted; assumes $Computer is a cluster node and $Path is CSV +function StopLogman +{ + [CmdletBinding()] + param( + [Parameter( + Mandatory = $true + )] + [string[]] + $Computer, + + [Parameter()] + [string] + $Name = "", + + [Parameter()] + [string] $Path + ) + + $n = "perfctr" + if ($Name.Length) { $n += "-$Name" } + + Invoke-CommonCommand -ComputerName $Computer -InitBlock $CommonFunc -ScriptBlock { + $f = Join-Path $env:TEMP "$($using:n)-$($env:COMPUTERNAME).blg" + + $o = logman stop $using:n 2>&1 + $s = $? + $m = "logman stop @ $env:COMPUTERNAME : $o" + if (-not $s) { Write-Error $m } else { Write-Verbose $m } + + $o = logman delete $using:n 2>&1 + $s = $? + $m = "logman delete @ $env:COMPUTERNAME : $o" + if (-not $s) { Write-Error $m } else { Write-Verbose $m } + + if ($using:Path.Length) + { + $o = xcopy /j /y /q $f $using:Path 2>&1 + $s = $? + $m = "xcopy @ $env:COMPUTERNAME : $o" + if (-not $s) { Write-Error $m } else { Write-Verbose $m } + } + + Remove-Item -Force $f + } +} + +# +# Invoke commands with a common initialization block of utility fns. +# When -AsJob, the sessions are persisted onto the parent job object +# for later cleanup on completion. +# +function Invoke-CommonCommand +{ + [CmdletBinding(DefaultParameterSetName = "Default")] + param( + + [Parameter(ParameterSetName = "Default")] + [Parameter(ParameterSetName = "AsJob")] + [Parameter(Position = 0)] + [string[]] + $ComputerName = @(), + + [Parameter(ParameterSetName = "Default")] + [Parameter(ParameterSetName = "AsJob")] + [scriptblock] + $InitBlock, + + [Parameter( + ParameterSetName = "Default", + Mandatory = $true + )] + [Parameter( + ParameterSetName = "AsJob", + Mandatory = $true + )] + [scriptblock] + $ScriptBlock, + + [Parameter(ParameterSetName = "Default")] + [Parameter(ParameterSetName = "AsJob")] + [Object[]] + $ArgumentList, + + [Parameter( + ParameterSetName = "AsJob", + Mandatory = $true + )] + [switch] + $AsJob, + + [Parameter(ParameterSetName = "AsJob")] + [string] + $JobName + ) + + $Sessions = @() + if ($ComputerName.Count -eq 0) + { + $Sessions = New-PSSession -Cn localhost -EnableNetworkAccess + } + else + { + $Sessions = New-PSSession -ComputerName $ComputerName + } + + # Guarantee session cleanup if exception thrown w/o either attaching to job + # for later removal or synch completion of work. + try + { + # Replay the verbose preference onto the seesions so logging is passed + Invoke-Command -Session $Sessions { $VerbosePreference = $using:VerbosePreference } + + if ($null -ne $InitBlock) + { + Invoke-Command -Session $Sessions $InitBlock + } + + switch($PSCmdlet.ParameterSetName) + { + "AsJob" + { + Invoke-Command -Session $Sessions -AsJob -JobName $JobName -ScriptBlock $ScriptBlock -ArgumentList $ArgumentList | + Add-Member -NotePropertyName ActiveSession -NotePropertyValue $Sessions.Id -PassThru + + # Attached, no cleanup here. + $Sessions = @() + } + + default + { + Invoke-Command -Session $Sessions -ScriptBlock $ScriptBlock -ArgumentList $ArgumentList + $Sessions | Remove-PSSession + } + } + } + finally + { + $Sessions | Remove-PSSession + } +} + +function RemoveCommonJobSession +{ + param ( + [Parameter(ValueFromPipeline = $true)] + [object] + $j + ) + + # Remove sessions from completed CommonCommand jobs + + process { + if (Get-Member -InputObject $j ActiveSession) + { + Remove-PSSession -Id $j.ActiveSession + } + } +} + +####################### + +function Get-FleetVersion +{ + [CmdletBinding()] + param ( + [Parameter()] + [string] + $Cluster = "." + ) + + $path = Get-FleetPath -PathType Tools @PSBoundParameters + + $vmfleet = Get-Command Get-FleetVersion + [PSCustomObject]@{ + Component = $vmfleet.Source + Version = $vmfleet.Version + } + + $diskspdPath = (Join-Path $path 'diskspd.exe') + if (Test-Path $diskspdPath) + { + $diskspd = Get-Command $diskspdPath + [PSCustomObject]@{ + Component = $diskspd.Name + Version = $diskspd.Version + } + } +} + +function Get-FleetPauseEpoch +{ + [CmdletBinding(DefaultParameterSetName = "ByCluster")] + param ( + [Parameter(ParameterSetName = "ByCluster")] + [string] + $Cluster = ".", + + [Parameter(ParameterSetName = "ByPath")] + [string] + $Path + ) + + switch($PSCmdlet.ParameterSetName) + { + "ByCluster" { $Path = Get-FleetPath -PathType Pause @PSBoundParameters } + } + + $ep = Get-Content $Path -ErrorAction SilentlyContinue + if ($null -eq $ep) + { + $ep = 0 + } + + $ep +} + +# +# Get-FleetPause +# +function Get-FleetPause +{ + [CmdletBinding(DefaultParameterSetName = "ByCluster")] + param ( + [Parameter(ParameterSetName = "ByCluster")] + [string] + $Cluster = ".", + + [Parameter(ParameterSetName = "ByPath")] + [string] + $Path + ) + + $pauseEpoch = Get-FleetPauseEpoch @PSBoundParameters -ErrorAction SilentlyContinue + + # non-0 indicates VMs are paused + $pauseEpoch -ne 0 +} + +# +# Show-Pause - text report of pause state +# +function Show-FleetPause +{ + [CmdletBinding(DefaultParameterSetName = "ByCluster")] + param ( + [Parameter(ParameterSetName = "ByCluster")] + [string] + $Cluster = ".", + + [Parameter(ParameterSetName = "ByPath")] + [string] + $Path + ) + + $pausePath = Get-FleetPath -PathType Pause @PSBoundParameters + $pauseEpoch = Get-FleetPauseEpoch -Path $pausePath -ErrorAction SilentlyContinue + if ($pauseEpoch -eq 0) + { + Write-Host -ForegroundColor red "Pause not in force" + return + } + + # Now correlate to online vms and see if we agree all online are paused. + + $vms = @(Get-ClusterGroup |? GroupType -eq VirtualMachine |? Name -like 'vm-*' |? State -eq Online) + $pausedVMs = @($vms | FilterPausedVMs -Path $pausePath) + + if ($pausedVMs.Count -eq $vms.Count) + { + Write-Host -ForegroundColor green "OK: All $($vms.count) VMs paused" + } + else + { + Write-Host -fore red "WARNING: of $($vms.Count), still waiting on $($vms.Count - $pausedVMs.Count) to acknowledge pause" + + Compare-Object $vms $pausedVMs -Property Name -PassThru | Sort-Object -Property Name | Format-Table -AutoSize Name,OwnerNode | Out-Host + } +} + +function FilterPausedVMs +{ + [CmdletBinding()] + param( + [Parameter(ParameterSetName = "ByPath")] + [string] + $Path, + + [Parameter(ValueFromPipeline = $true)] + [Microsoft.FailoverClusters.PowerShell.ClusterGroup[]] + $VM + ) + + begin + { + $pauseEpoch = Get-FleetPauseEpoch -Path $Path -ErrorAction SilentlyContinue + + # accumulate hash of pause flags mapped to current/stale state + $h = @{} + + Get-ChildItem "$($Path)-*" |% { + + $thisPause = Get-Content $_ -ErrorAction SilentlyContinue + if ($thisPause -eq $pauseEpoch) + { + $pauseType = [PauseState]::Current + } + else + { + $pauseType = [PauseState]::Stale + } + + if ($_.name -match 'pause-(vm.+)') + { + # VM Name + $h[$matches[1]] = $pauseType + } + else + { + Write-Warning "malformed pause $($_.Name) present" + } + } + } + + process + { + # Pass through all VMs which have responsed to the current pause. + if ($h[$VM.Name] -eq [PauseState]::Current) + { + $VM + } + } +} + +function Set-FleetPause +{ + [CmdletBinding(DefaultParameterSetName = "ByCluster")] + param ( + [Parameter(ParameterSetName = "ByCluster")] + [string] + $Cluster = ".", + + [Parameter(ParameterSetName = "ByPath")] + [string] + $Path, + + [Parameter()] + [ValidateRange(0,120)] + [int] + $Timeout = 120, + + [Parameter()] + [switch] + $Force + ) + + # Parameter set specifying cluster only + $clusterParam = @{} + CopyKeyIf $PSBoundParameters $clusterParam 'Cluster' + + $pausePath = Get-FleetPath @clusterParam -PathType Pause + + if ((-not $Force) -and (Get-FleetPause -Path $pausePath)) + { + LogOutput -IsVb "pause is already set @ $((Get-Item $pausePath).LastWriteTime)" + return + } + + # if forcing (live check), there may be a nonzero existing pause. + # ensure the new nonzero pause flag is distinct. + $currentPause = Get-FleetPauseEpoch -Path $pausePath + do { + $newPause = Get-Random -Minimum 1 + if ($newPause -eq $currentPause) { continue } + Write-Output $newPause > $pausePath + break + } until ($false) + + LogOutput -IsVb "pause set @ $((Get-Item $pausePath).LastWriteTime)" + + # Wait for pause to settle? + if ($Timeout -gt 0) + { + $t0 = Get-Date + $vms = @(Get-ClusterGroup @clusterParam |? GroupType -eq VirtualMachine |? Name -like 'vm-*' |? State -eq Online) + + do { + + $td = (Get-Date) - $t0 + + $pausedVMs = @($vms | FilterPausedVMs -Path $pausePath) + if ($pausedVMs.Count -eq $vms.Count) + { + break + } + + if ($td.TotalSeconds -gt $Timeout) + { + Write-Error "Wait for pause ack timed out - only $($pausedVMs.Count)/$($vms.Count) pause responses. Check fleet health with Get-FleetVM -ControlResponse and Repair-Fleet if needed." + return + } + + LogOutput -IsVb "WAIT pause ack @ $($pausedVMs.Count)/$($vms.Count) paused" + Start-Sleep 2 + + } while ($true) + + LogOutput -IsVb "pause ack $($vms.Count) total ($('{0:0.0}' -f $td.TotalSeconds)s to ack)" + } +} + +function Clear-FleetPause +{ + [CmdletBinding(DefaultParameterSetName = "ByCluster")] + param ( + [Parameter(ParameterSetName = "ByCluster")] + [string] + $Cluster = ".", + + [Parameter(ParameterSetName = "ByPath")] + [string] + $Path + ) + + $pausePath = Get-FleetPath -PathType Pause @PSBoundParameters + + if (-not (Get-FleetPause -Path $pausePath)) + { + LogOutput -IsVb "pause is already cleared @ $((Get-Item $pausePath).LastWriteTime)" + return + } + + Write-Output 0 > $pausePath + LogOutput -IsVb "pause cleared @ $((Get-Item $pausePath).LastWriteTime)" +} + +function Install-Fleet +{ + [CmdletBinding()] + param ( + [Parameter()] + [string] + $Cluster = ".", + + [Parameter()] + [switch] + $KeepBlockCache, + + [Parameter()] + [switch] + $NoTools, + + [Parameter()] + [switch] + $Force + ) + + # Parameter set specifying cluster only + $clusterParam = @{} + CopyKeyIf $PSBoundParameters $clusterParam 'Cluster' + + # + # Ensure there is a collect volume and at least 1 CSV with prefix per + # node of the cluster. + # + + $csvs = @(GetMappedCSV @clusterParam) + $nodes = @(Get-ClusterNode @clusterParam) + + # Now accumulate the other potential configuration errors so we can emit all of them before stopping + $e = $false + + # Ensure there is a collect volume. + if ($csvs.Count -eq 0 -or $csvs.VDName -notcontains $collectVolumeName) + { + $errorObject = CreateErrorRecord -ErrorId "No $($collectVolumeName) CSV found" ` + -ErrorMessage "VM Fleet requires one volume named '$($collectVolumeName)' which will hold VM Fleet data/scripts" ` + -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` + -Exception $null ` + -TargetObject $null + + $psCmdlet.WriteError($errorObject) + $e = $true + } + else + { + LogOutput -IsVb "collect volume exists" + } + + # Ensure there is at least one volume per node. Note this is warning since the volume size estimator is neccesarily part + $h = @{ } + foreach ($node in $nodes) + { + $h[$node] = 0 + } + + foreach ($csv in $csvs) + { + foreach ($node in $nodes) + { + if ($csv.VDName -match "^$node(?:-.+){0,1}$") + { + LogOutput -IsVb "volume $($csv.VDName) matched to node $node" + $h[$node] += 1 + break + } + } + } + + foreach ($node in $nodes) + { + if ($h[$node] -eq 0) + { + $errorObject = CreateErrorRecord -ErrorId "No CSV found for node" ` + -ErrorMessage "Node $($node): VM Fleet requires at least one volume prefixed with node name which will be used to hold VM data/metadata. Use Get-FleetVolumeEstimate for recommended sizings." ` + -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` + -Exception $null ` + -TargetObject $null + + $psCmdlet.WriteError($errorObject) + $e = $true + } + } + + # Now halt if volume configuration was incomplete/missing. + if ($e) + { + return + } + + # Install directory structure + + $paths = ($controlPath, $flagPath, $resultPath, $toolsPath) = Get-FleetPath -PathType Control,Flag,Result,Tools @clusterParam + foreach ($path in $paths) + { + if (-not (Test-Path $path)) + { + LogOutput -IsVb "installing path: $path" + $null = New-Item -Path $path -ItemType Directory + } + else + { + LogOutput -IsVb "path exists: $path" + } + } + + # Set fleet pause so that VMs come up idle + Set-FleetPause -Timeout 0 + + # + # Unwrap VM scripts (control, run*) + # + + $f = Join-Path $controlPath "control.ps1" + if (-not (Test-Path $f) -or $Force) + { + $c = Get-Command Set-FleetControlScript + if (Test-Path $f) { Remove-Item $f -Force } + $c.ScriptBlock | Out-File -FilePath "$($f).tmp" -Width ([int32]::MaxValue) -Force + Move-Item "$($f).tmp" $f + } + + $f = Join-Path $controlPath "run.ps1" + if (-not (Test-Path $f) -or $Force) + { + $c = Get-Command Set-FleetBaseScript + if (Test-Path $f) { Remove-Item $f -Force } + $c.ScriptBlock | Out-File -FilePath "$($f).tmp" -Width ([int32]::MaxValue) -Force + Move-Item "$($f).tmp" $f + } + + if (-not $KeepBlockCache) + { + LogOutput -IsVb "disabling CSV block cache (recommended)" + (Get-Cluster @clusterParam).BlockCacheSize = 0 + } + + if (-not $NoTools) + { + # DISKSPD + + $diskspdPath = (Join-Path $toolsPath "diskspd.exe") + $exist = Test-Path $diskspdPath + if (-not $exist -or $Force) + { + if ($exist) + { + LogOutput -IsVb "removing prior install of DISKSPD" + Remove-Item -Force $diskspdPath + } + + try + { + # Temporary ZIP file. Expand-Archive insists on the extension. + $z = New-TemporaryFile + $z = Rename-Item $z $($z.BaseName + ".zip") -PassThru + + # Temporary directory for extraction. + $d = New-TemporaryFile + Remove-Item $d + $d = New-Item -ItemType Directory $d + + LogOutput -IsVb "downloading DISKSPD" + + try { + + $diskspdURI = "https://aka.ms/getdiskspd" + + # Force TLS1.2. Downlevel TLS defaults may be rejected. Restore after download. + $oldTls = [Net.ServicePointManager]::SecurityProtocol + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Invoke-WebRequest -Uri $diskspdURI -OutFile $z + [Net.ServicePointManager]::SecurityProtocol = $oldTls + + Expand-Archive $z -DestinationPath $d + + # Now pull the architecture appropriate diskspd and remove the zip content + Copy-Item (Join-Path $d (Join-Path $env:PROCESSOR_ARCHITECTURE "diskspd.exe")) $toolsPath + } + catch + { + Write-Warning "Cannot download DISKSPD from public site @ $diskspdURI : please acquire and place @ $toolsPath before running loads requiring it " + } + + } + finally + { + if (Test-Path $z) { Remove-Item $z -Force } + if (Test-Path $d ) { Remove-Item $d -Recurse -Force} + } + } + else + { + LogOutput -IsVb "DISKSPD already present" + } + } + + # + # Speedbrake: Fleet requires DISKSPD >= 2.1.0.0 for new features + # + + $v = Get-FleetVersion + $diskspd = $v |? Component -eq diskspd.exe + $vmfleet = $v |? Component -eq VMFleet + + if ($null -eq $diskspd) + { + Write-Error ("DISKSPD is not present. VM Fleet {0} requires DISKSPD for its core measurement functionality. Please update and, if running, restart the fleet to pick up the tool." -f ( + $vmfleet.Version)) + } + elseif ($diskspd.Version.CompareTo([Version]'2.1.0.0') -lt 0) + { + Write-Error ("The installed DISKSPD is version {0}. VM Fleet {1} requires the functionality of DISKSPD 2.1.0.0. Please update and, if running, restart the fleet to pick up the new version." -f ( + $diskspd.Version, + $vmfleet.Version)) + } +} + +function New-Fleet +{ + [CmdletBinding(DefaultParameterSetName = "ByCluster")] + param( + [Parameter(ParameterSetName = "ByCluster")] + [string] + $Cluster = ".", + + [Parameter(ParameterSetName = "ByNode", Mandatory = $true)] + <# + [ArgumentCompleter({ + param ( $commandName, + $parameterName, + $wordToComplete, + $commandAst, + $fakeBoundParameters ) + # Perform calculation of tab completed values here. + })] + #> + [string[]] + $Node, + + [Parameter(ParameterSetName = "ByCluster")] + [Parameter(ParameterSetName = "ByNode")] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $BaseVHD, + + [Parameter(ParameterSetName = "ByCluster")] + [Parameter(ParameterSetName = "ByNode")] + [ValidateRange(1, 4096)] + [int] + $VMs, + + [Parameter(ParameterSetName = "ByCluster")] + [Parameter(ParameterSetName = "ByNode")] + [string[]] + $Groups = @(), + + [Parameter(ParameterSetName = "ByCluster")] + [Parameter(ParameterSetName = "ByNode")] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $AdminPass, + + [Parameter(ParameterSetName = "ByCluster")] + [Parameter(ParameterSetName = "ByNode")] + [ValidateNotNullOrEmpty()] + [string] + $Admin = 'administrator', + + [Parameter(ParameterSetName = "ByCluster")] + [Parameter(ParameterSetName = "ByNode")] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $ConnectPass, + + [Parameter(ParameterSetName = "ByCluster")] + [Parameter(ParameterSetName = "ByNode")] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $ConnectUser, + + [Parameter(ParameterSetName = "ByCluster")] + [Parameter(ParameterSetName = "ByNode")] + [ValidateSet('CreateVMSwitch','CopyVHD','CreateVM','CreateVMGroup','AssertComplete')] + [string] + $StopAfter, + + [Parameter(ParameterSetName = "ByCluster")] + [Parameter(ParameterSetName = "ByNode")] + [ValidateSet('Force','Auto','None')] + [string] + $Specialize = 'Auto', + + [Parameter(ParameterSetName = "ByCluster")] + [Parameter(ParameterSetName = "ByNode")] + [switch] + $KeepVHD + ) + + # Parameter set specifying cluster only + $clusterParam = @{} + CopyKeyIf $PSBoundParameters $clusterParam 'Cluster' + + ################## + + # check if Arc enabled VMs should be created + $createArcVMs = $false + $arcConfig = Get-ArcConfig -Cluster $Cluster + $resourceGroup, $azUser, $azPassword, $storagePath, $image, $salt = $null + if($arcConfig -and $arcConfig.Enabled){ + $createArcVMs = $true + $resourceGroup, + $azUser, + $azPassword, + $storagePath, + $image, + $salt = + $arcConfig.ResourceGroup, + $arcConfig.AzureRegistrationUser, + $arcConfig.AzureRegistrationPassword, + $arcConfig.StoragePathName, + $arcConfig.ImageName, + $arcConfig.Salt + LogOutput "Create Arc-enabled Virtual Machines? $createArcVMs" + } + + # validate existence of BaseVHD + if (-not (Test-Path -Path $BaseVHD)) { + $errorObject = CreateErrorRecord -ErrorId "No BaseVHD" ` + -ErrorMessage "The base VHD path $BaseVHD was not found" ` + -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` + -Exception $null ` + -TargetObject $null + + $psCmdlet.WriteError($errorObject) + return + } + + # resolve BaseVHD to fully qualified path + $BaseVHD = (Get-Item $BaseVHD).FullName + + if (@(Get-ClusterNode @clusterParam |? State -ne Up).Count -ne 0) { + $errorObject = CreateErrorRecord -ErrorId "Cluster nodes not up" ` + -ErrorMessage "Some nodes of cluster $Cluster are not up - please address before creating the fleet" ` + -ErrorCategory ([System.Management.Automation.ErrorCategory]::ResourceUnavailable) ` + -Exception $null ` + -TargetObject $null + + $psCmdlet.WriteError($errorObject) + return + } + + switch ($psCmdlet.ParameterSetName) + { + "ByCluster" { $Nodes = @(Get-ClusterNode @clusterParam) } + } + + # convert to fixed vhd(x) if needed + if ((Get-Vhd $BaseVHD).VhdType -ne 'Fixed' -and -not $KeepVHD) { + + # push dynamic vhd to tmppath and place converted at original + # note that converting a dynamic will leave a sparse hole on refs + # this is OK, since the copy will not copy the hole + $f = Get-Item $BaseVHD + $tmpname = "tmp-$($f.Name)" + $tmppath = Join-Path $f.DirectoryName $tmpname + Remove-Item -Force $tmppath -ErrorAction SilentlyContinue + Rename-Item $f.FullName $tmpname + + LogOutput -IsVb "convert $($f.FullName) to fixed via $tmppath" + Convert-VHD -Path $tmppath -DestinationPath $f.FullName -VHDType Fixed + if (-not $?) { + Rename-Item $tmppath $f.Name + + # allow likely error from Convert-VHD to flow out + return + } + + Remove-Item $tmppath + } + + # + # Autoscaled VMs (1 per physical core) if not specified + # + + if (-not $PSBoundParameters.ContainsKey('VMs')) + { + $g = @(Get-CimInstance -CimSession $nodes.Name Win32_Processor | Group-Object -Property NumberOfEnabledCore) + + if ($g.Count -eq 0) + { + $errorObject = CreateErrorRecord -ErrorId "Cannot query enabled processor cores" ` + -ErrorMessage "Cannot query enabled processor core from cluster nodes" ` + -ErrorCategory ([System.Management.Automation.ErrorCategory]::ResourceUnavailable) ` + -Exception $null ` + -TargetObject $null + + $psCmdlet.WriteError($errorObject) + return + } + + if ($g.Count -ne 1 -or $g[0].Group.Count % $nodes.Count) + { + $errorObject = CreateErrorRecord -ErrorId "Non-uniform processors" ` + -ErrorMessage "Cluster $Cluster nodes have processors with differing numbers of enabled processor cores - please verify configuration" ` + -ErrorCategory ([System.Management.Automation.ErrorCategory]::ResourceUnavailable) ` + -Exception $null ` + -TargetObject $null + + $psCmdlet.WriteError($errorObject) + return + } + + $VMs = ($g[0].Group.NumberOfEnabledCore | Measure-Object -Sum).Sum / $nodes.Count + + LogOutput -IsVb "Autoscaling number of VMs to $VMs / node" + } + + # Create the fleet vmswitches with a fixed IP at the base of the APIPA range + Invoke-CommonCommand $nodes -InitBlock $CommonFunc -ScriptBlock { + + if (-not (Get-VMSwitch -Name $using:vmSwitchName -ErrorAction SilentlyContinue)) { + + $null = New-VMSwitch -name $using:vmSwitchName -SwitchType Internal + $null = Get-NetAdapter |? DriverDescription -eq 'Hyper-V Virtual Ethernet Adapter' |? Name -eq "vEthernet ($($using:vmSwitchName))" | New-NetIPAddress -PrefixLength 16 -IPAddress $using:vmSwitchIP + LogOutput "CREATE internal vmswitch $($using:vmSwitchName) @ $($env:COMPUTERNAME)" + } + } + + #### STOPAFTER + if (Stop-After "CreateVMSwitch" $stopafter) { + return + } + + # create $vms vms per each csv named as + # vm name is vm-<$group>-- + + # note that this would pass as a shallow copy of the object; this is why they are unpacked + # into a (flat) custom object + $csvs = GetMappedCSV @clusterParam |% { [PSCustomObject]@{ + VDName = $_.VDName + FriendlyVolumeName = $_.SharedVolumeInfo.FriendlyVolumeName + }} + + # Create Gallery Image and Storage Path Arc resources if Arc VM flag set to true + if ($createArcVMs) { + if(-not ($arcConfig | Get-Member -Name 'StoragePathCsv' -MemberType Properties)){ + foreach ($csv in $csvs) { + # identify the Csv which can be used to create storage path + # the trailing characters (if any) are the group prefix + if ($csv.VDName -match "^$env:COMPUTERNAME(?:-.+){0,1}") { + $arcConfig | Add-Member -MemberType NoteProperty -Name "StoragePathCsv" -Value $csv.FriendlyVolumeName + $storagePathCsv = $arcConfig.StoragePathCsv + break + } + } + } + $result = Invoke-CommonCommand $nodes[0] -InitBlock $CommonFunc -ArgumentList @($BaseVHD) -ScriptBlock { + param($vhdPath) + $retryCount = 0 + while ($retryCount -lt 3) { + try { + $extendedLocation = InitializeAndGetArcHCIExtendedLoc $using:azUser $using:azPassword $using:resourceGroup + if (-not $extendedLocation) { + LogOutput -ForegroundColor Red "Error generating Extended Location value" + return "Error occurred while getting extended location" + } + + $location = (Get-AzureStackHCI | select Region).Region + + $spId = $null + $spResult = az stack-hci-vm storagepath list --resource-group $using:resourceGroup --query "[?name=='$($using:storagePath)' && properties.provisioningState=='Succeeded'].id" --output tsv + if ($null -ne $spResult) { + LogOutput "Storage path $using:storagePath already exists" + } + elseif (az stack-hci-vm storagepath list --resource-group $using:resourceGroup --query "[?name=='$($using:storagePath)' && properties.provisioningState=='Failed'].id" --output tsv) { + LogOutput "$using:storagePath exists with provision status Failed. Deleting existing storage path and recreating..." + az stack-hci-vm storagepath delete --name $using:storagePath --resource-group $using:resourceGroup --yes + $spResult = az stack-hci-vm storagepath create --name $using:storagePath --resource-group $using:resourceGroup --custom-location $extendedLocation --path $using:storagePathCsv --location $location | ConvertFrom-Json + } + else { + LogOutput "Creating storage path $storagePath..." + $spResult = az stack-hci-vm storagepath create --name $using:storagePath --resource-group $using:resourceGroup --custom-location $extendedLocation --path $using:storagePathCsv --location $location | ConvertFrom-Json + } + + if (-not $spResult) { + throw "Failed to create storage path $using:storagePath" + } + if($spResult.Id) { + $spId = $spResult.Id + } + else { + $spId = $spResult + } + LogOutput "Initialized storage path: $using:storagePath, creating image with $vhdPath..." + + $imgResult = az stack-hci-vm image list --resource-group $using:resourceGroup --query "[?name=='$($using:image)' && properties.provisioningState=='Succeeded'].id" --output tsv + if ($null -ne $imgResult) { + LogOutput "Image $using:image already exists." + } + elseif (az stack-hci-vm image list --resource-group $using:resourceGroup --query "[?name=='$($using:image)' && properties.provisioningState=='Failed'].id" --output tsv) { + LogOutput "$using:image exists with provision status Failed. Deleting existing image and recreating..." + az stack-hci-vm image delete --name $using:image --resource-group $using:resourceGroup --yes + $imgResult = az stack-hci-vm image create --name $using:image --resource-group $using:resourceGroup --location $location --custom-location $extendedLocation --os-type Windows --storage-path-id $spId --image-path $vhdPath | ConvertFrom-Json + } + else { + LogOutput "Creating image $using:image..." + $imgResult = az stack-hci-vm image create --name $using:image --resource-group $using:resourceGroup --location $location --custom-location $extendedLocation --os-type Windows --storage-path-id $spId --image-path $vhdPath | ConvertFrom-Json + } + + if (-not $imgResult) { + throw "Failed to create image $using:image" + } + + LogOutput "Initialized Gallery image: $using:image" + LogOutput -ForegroundColor Green "Finished initialization of resources using Azure CLI" + break + } + catch { + $retryCount++ + if ($retryCount -eq 3) { + return "Error occurred while creating Azure resources for all retry counts" + } + } + } + } + if($result -ne $null -and $result.Contains("Error")){ + return $result + } + } + + #### STOPAFTER + if (Stop-After "CreateVMSwitch" $stopafter) { + return + } + + Invoke-CommonCommand $nodes -InitBlock $CommonFunc -ArgumentList @(,$csvs) -ScriptBlock { + param($csvs) + + foreach ($csv in $csvs) { + + if ($($using:groups).Length -eq 0) { + $groups = @( 'base' ) + } else { + $groups = $using:groups + } + + # identify the CSvs for which this node should create its VMs + # the trailing characters (if any) are the group prefix + if ($csv.VDName -notmatch "^$env:COMPUTERNAME(?:-.+){0,1}") { + continue + } + + foreach ($group in $groups) { + + if ($csv.VDName -match "^$env:COMPUTERNAME-([^-]+)$") { + $g = $group+$matches[1] + } else { + $g = $group + } + + foreach ($vm in 1..$using:vms) { + + $stop = $false + + $newvm = $false + if($using:createArcVMs) { + $name = ("vm-$g-$env:COMPUTERNAME-$using:salt-{0:000}" -f $vm) + } + else { + $name = ("vm-$g-$env:COMPUTERNAME-{0:000}" -f $vm) + } + + $placePath = $csv.FriendlyVolumeName + $vmPath = Join-Path $placePath $name + $vhdPath = Join-Path $vmPath "$name.vhdx" + + # if the vm cluster group exists, we are already deployed + if (-not (Get-ClusterGroup -Name $name -ErrorAction SilentlyContinue)) { + + if (-not $stop) { + $stop = Stop-After "AssertComplete" + } + + if ($stop) { + + LogOutput -ForegroundColor Red "vm $name not deployed" + + } else { + + LogOutput "CREATE vm $name @ path $vmPath" + + # always specialize new vms + $newvm = $true + + # We're on the canonical node to create the vm; if the vm exists, tear it down and refresh + $o = Get-VM -Name $name -ErrorAction SilentlyContinue + + if ($null -ne $o) { + + # interrupted between vm creation and role creation; redo it + LogOutput "REMOVE vm $name for re-creation" + + if ($o.State -ne 'Off') { + Stop-VM -Name $name -TurnOff -Force -Confirm:$false + } + if($using:createArcVMs){ + az stack-hci-vm delete --name $name --resource-group $using:resourceGroup --yes + } + else { + Remove-VM -Name $name -Force -Confirm:$false + } + } + # we do not need to copy vhd for Arc vms. It is done by the product code itself + if($using:createArcVMs -eq $false){ + # scrub and re-create the vm metadata path and vhd + if (Test-Path $vmPath) + { + Remove-Item -Recurse $vmPath + } + + $null = New-Item -ItemType Directory $vmPath + Copy-Item $using:BaseVHD $vhdPath + + #### STOPAFTER + if (-not $stop) { + $stop = Stop-After "CopyVHD" $using:stopafter + } + + if (-not $stop) { + + # Create A1 VM. use set-vmfleet to alter fleet sizing post-creation. + # Do not monitor the internal switch connection; this allows live migration + # despite the internal switch. + $o = New-VM -VHDPath $vhdPath -Generation 2 -SwitchName $using:vmSwitchName -Path $placePath -Name $name + if ($null -ne $o) + { + $o | Set-VM -ProcessorCount 1 -MemoryStartupBytes 1.75GB -StaticMemory + $o | Get-VMNetworkAdapter | Set-VMNetworkAdapter -NotMonitoredInCluster $true + } + } + } + else { + $retrycount = 0 + while($retrycount -lt 3){ + try{ + $vmResult = $null + $extendedLocation = InitializeAndGetArcHCIExtendedLoc $using:azUser $using:azPassword $using:resourceGroup + $location = (Get-AzureStackHCI | select Region).Region + LogOutput "Creating Arc HCI VM - $name ..." + $vmResult = az stack-hci-vm create --name $name --resource-group $using:resourceGroup --admin-username $using:admin --admin-password $using:adminpass --computer-name $using:admin --location $location --enable-agent False --enable-vm-config-agent false --os-type "Windows" --custom-location $extendedLocation --image $using:image --storage-path-id $using:storagePath --hardware-profile memory-mb="2048" processors="1" vm-size="Custom" + LogOutput "Result returned for $name - $vmResult" + if($vmResult -eq $null){ + $msg = "Error creating Virtual machine $name at retry count $retryCount" + LogOutput -ForegroundColor Yellow $msg + throw $msg + } + break + } + catch { + $retrycount++ + if($retrycount -eq 3){ + LogOutput -ForegroundColor Yellow "Error occurred while creating $name for all retry counts" + $stop = $true + } + } + } + if (-not $stop) { + # validating creation of Arc VMs using powershell commands + # hyperv Arc VM names + $vmname = "Virtual Machine " + $name + $Stoploop = $false + $Retrycount = 0 + do { + try { + LogOutput "Executing Get-ClusterResource -Name $vmname" + $o = Get-ClusterResource -Name $vmname + if ($o -eq $null) { + throw "Null returned by Get-ClusterResource due to some error" + } + else { + LogOutput "Creation of vm completed at hyperv level" + $Stoploop = $true + } + } + catch { + if ($Retrycount -gt 10) { + LogOutput -ForegroundColor Red "Could not get VM $vmname after 10 retries using Get-ClusterResource" + $Stoploop = $true + } + else { + LogOutput -ForegroundColor Yellow "Could not get VM $vmname at $RetryCount retry count. Retrying in 6 minutes..." + Start-Sleep -Seconds 360 + $Retrycount += 1 + } + } + } + While ($Stoploop -eq $false) + } + } + #### STOPAFTER + if (-not $stop) { + $stop = Stop-After "CreateVM" $using:stopafter + } + + if (-not $stop -and $using:createArcVMs -ne $true) { + # Create clustered vm role (don't emit) and assign default owner node. + # Swallow the (expected) warning that will be referring to the internal + # vmswitch as being a local-only resource. Since we replicate the vswitch + # with the same IP on each cluster node, it does work. + # + # If an error occurs, it will emit per normal. + $null = $o | Add-ClusterVirtualMachineRole -WarningAction SilentlyContinue + Set-ClusterOwnerNode -Group $o.VMName -Owners $env:COMPUTERNAME + } + } + + } else { + LogOutput -ForegroundColor Green "vm $name already deployed" + } + + #### STOPAFTER + if (-not $stop) { + $stop = Stop-After "CreateVMGroup" $using:stopafter + } + + if ((-not $stop -or ($using:specialize -eq 'Force')) -and -not $using:createArcVMs) { + # specialize as needed + # auto only specializes new vms; force always; none skips it + if (($using:specialize -eq 'Auto' -and $newvm) -or ($using:specialize -eq 'Force')) { + LogOutput "SPECIALIZE vm $name" + if (-not (SpecializeVhd $vhdPath $using:admin $using:adminpass $using:connectuser $using:connectpass)) { + LogOutput -ForegroundColor red "Failed specialize of vm $name @ $vhdPath, halting." + } + } else { + LogOutput -ForegroundColor green skip specialize $vhdPath + } + } + } + } + } + } + + if($createArcVMs){ + Stop-Fleet + LogOutput "Updating network adapter for Fleet to add internal switch" + $clusterName = (Get-Cluster).Name + $nodes = @(Get-ClusterNode -Cluster $clusterName | ? State -eq Up) + $regPattern = "^vm-.{1,}-" + $salt + "-\d{3}$" + foreach ($o in Get-VM -CimSession $nodes.Name | ? Name -match $regPattern) { + $o | Add-VMNetworkAdapter -SwitchName $vmSwitchName + $o | Get-VMNetworkAdapter | Connect-VMNetworkAdapter -SwitchName $vmSwitchName + $o | Get-VMNetworkAdapter | Set-VMNetworkAdapter -NotMonitoredInCluster $true + Set-ClusterOwnerNode -Group $o.VMName -Owners $env:COMPUTERNAME + LogOutput "Network Adapter attached to VM" + } + Stop-Fleet + Initialize-ArcVMs $admin $adminpass $connectuser $connectpass $salt + } +} + +# Initialize Arc VMs +function Initialize-ArcVMs ($admin ,$adminpass, $connectuser, $connectpass, $salt){ + $clusterName = (Get-Cluster).Name + $nodes = @(Get-ClusterNode -Cluster $clusterName | ? State -eq Up) + $regPattern = "^vm-.{1,}-" + $salt + "-\d{3}$" + foreach ($vm in Get-VM -CimSession $nodes.Name | ? Name -match $regPattern) { + $vmname = "Virtual Machine " + $vm.Name + $name = $vm.Name + $vhdPath = $vm | Select-Object -ExpandProperty HardDrives | Where-Object Path -like "*OSDisk*.vhdx" | Select Path + if($vhdPath -eq $null){ + LogOutput -ForegroundColor red "Could not find vhd path for $vmname." + } + $path = $vhdPath.Path + if (-not (SpecializeVhd $path $admin $adminpass $connectuser $connectpass $name)) { + LogOutput -ForegroundColor red "Failed specialize of vm $name @ $path, halting." + } + else { LogOutput -ForegroundColor green "Successfully Specialized $vmname" } + } +} + +function Remove-Fleet +{ + [CmdletBinding()] + param( + [Parameter()] + [string] + $Cluster = ".", + + [Parameter()] + [string[]] + $VMs + ) + + # Parameter set specifying cluster only + $clusterParam = @{} + CopyKeyIf $PSBoundParameters $clusterParam 'Cluster' + + # check if Arc enabled VMs should be created + $arcConfig = Get-ArcConfig -Cluster $Cluster + $arcEnvEnabled = $arcConfig -and $arcConfig.Enabled + $resourceGroup, $azUser, $azPassword, $storagePath, $img, $salt = $null + if($arcEnvEnabled){ + $resourceGroup, + $azUser, + $azPassword, + $storagePath, + $img, + $salt = + $arcConfig.ResourceGroup, + $arcConfig.AzureRegistrationUser, + $arcConfig.AzureRegistrationPassword, + $arcConfig.StoragePathName, + $arcConfig.ImageName, + $arcConfig.Salt + LogOutput "Is environment HCI Arc-enabled? $arcEnvEnabled" + } + + if ($vms) + { + LogOutput -IsVb "Removing VM Fleet content for: $vms" + } + else + { + LogOutput -IsVb "Removing VM Fleet" + } + + # stop and remove clustered vm roles + Get-ClusterGroup @clusterParam |? GroupType -eq VirtualMachine |? { + + if ($vms) + { + $_.Name -in $vms + } + elseif($arcEnvEnabled) + { + $_.Name -match "^vm-.{1,}-" + $salt + "-\d{3}$" + } + else + { + $_.Name -like 'vm-*' + } + } |% { + + LogOutput -IsVb "Removing ClusterGroup for $($_.Name)" + $null = $_ | Stop-ClusterGroup + $_ | Remove-ClusterGroup -RemoveResources -Force + } + + # Capture flag file path for the installation + $flagPath = Get-FleetPath -PathType Flag @clusterParam + + # remove all vms + Invoke-CommonCommand (Get-ClusterNode @clusterParam) -InitBlock $CommonFunc -ScriptBlock { + + Get-VM |? { + if ($using:vms) + { + $_.Name -in $using:vms + } + elseif($using:arcEnvEnabled) + { + $_.Name -match "^vm-.{1,}-" + $using:salt + "-\d{3}$" + } + else + { + $_.Name -like 'vm-*' + } + } |% { + + LogOutput -IsVb "Removing VM for $($_.Name) @ $($env:COMPUTERNAME)" + if($using:arcEnvEnabled) { + InitializeAndGetArcHCIExtendedLoc $using:azUser $using:azPassword $using:resourceGroup + $vmName = $_.Name + az stack-hci-vm delete --name $vmName --resource-group $using:resourceGroup --yes + LogOutput "Removed $vmName" + } + else { + $_ | Remove-VM -Confirm:$false -Force + # remove hosting directory and any flag files associated with this VM + Remove-Item $_.Path -Recurse -Force + } + + $f = Join-Path $using:flagPath "*-$($_.Name)" + if (Test-Path $f) + { + Remove-Item $f + } + } + + # do not remove the internal switch if teardown is partial + if ($null -eq $using:vms) + { + $switch = Get-VMSwitch -SwitchType Internal + if ($null -ne $switch) + { + LogOutput -IsVb "Removing Internal VMSwitch @ $($env:COMPUTERNAME)" + $switch | Remove-VMSwitch -Confirm:$False -Force + } + } + } + + if($arcEnvEnabled){ + $nodes = Get-ClusterNode @clusterParam + Invoke-CommonCommand $nodes[0] -InitBlock $CommonFunc -ScriptBlock { + InitializeAndGetArcHCIExtendedLoc $using:azUser $using:azPassword $using:resourceGroup + $imgList = az stack-hci-vm image list --resource-group $using:resourceGroup | ConvertFrom-Json + $existingImage = $imgList | Where-Object name -eq $using:img + if($existingImage){ + LogOutput "Deleting image - $($using:img)" + az stack-hci-vm image delete --name $using:img --resource-group $using:resourceGroup --yes + } + $spList = az stack-hci-vm storagepath list --resource-group $using:resourceGroup | ConvertFrom-Json + $existingSP = $spList | Where-Object name -eq $using:storagePath + if($existingSP){ + LogOutput "Deleting Storage Path - $($using:storagePath)" + az stack-hci-vm storagepath delete --name $using:storagePath --resource-group $using:resourceGroup --yes + } + } + } + + # Fallback to clear remnant content due to earlier errors/etc. + + # Now delete content from csvs corresponding to the cluster nodes + $csv = GetMappedCSV @clusterParam + + Get-ClusterNode |% { + $csv |? VDName -match "$($_.Name)(-.+)?" + } |% { + + Get-ChildItem -Directory $_.sharedvolumeinfo.friendlyvolumename |? { + if ($vms) + { + $_.Name -in $vms + } + else + { + $_.Name -like 'vm-*' + } + } |% { + + LogOutput -IsVb "Removing CSV content for $($_.BaseName) @ $($_.FullName)" + if(-not $arcEnvEnabled){ + # remove hosting directory and any flag files associated with this VM + Remove-Item $_.FullName -Recurse -Force + } + $f = Join-Path $flagPath "*-$($_.Name)" + if (Test-Path $f) + { + Remove-Item $f + } + } + } +} + +function Start-Fleet +{ + [CmdletBinding(DefaultParameterSetName = "ByPercent")] + param( + [Parameter(ParameterSetName = "ByNumber")] + [Parameter(ParameterSetName = "ByPercent")] + [string] + $Cluster = ".", + + [Parameter(ParameterSetName = "ByNumber")] + [Parameter(ParameterSetName = "ByPercent")] + [Parameter()] + [string[]] + $Group = @("*"), + + [Parameter(ParameterSetName = "ByNumber", Mandatory = $true)] + [ValidateRange(1, [uint32]::MaxValue)] + [int] + $Number, + + [Parameter(ParameterSetName = "ByPercent")] + [ValidateRange(1, 100)] + [double] + $Percent = 100 + ) + + # Parameter set specifying cluster only + $clusterParam = @{} + CopyKeyIf $PSBoundParameters $clusterParam 'Cluster' + + $node = @(Get-ClusterNode @clusterParam) + $numNode = $node.Count + + # Pause and clear fleet run state so that VMs arrive as a coordinated group + Set-FleetPause @clusterParam -Timeout 0 + Clear-FleetRunState @clusterParam + + Invoke-CommonCommand ($node |? State -eq Up) -InitBlock $CommonFunc -ScriptBlock { + + $n = $using:Number + $pct = $using:Percent + + $using:Group |% { + + # Get group vms - assume symmetry per node + $vms = @(Get-ClusterGroup |? GroupType -eq VirtualMachine |? Name -like "vm-$_-*") + + # + # Start number/percentage is keyed by VM number label, not dependent on current + # position of VMs within the cluster. For instance, starting 5 VMs/node will start + # vm-etc-001, 002, 003, 004, and 005 wherever they are. + # + # This allows composition with Move-Fleet and partial redistribution of VMs. + # + + # Start subset up to VM #n? + $(if (0 -ne $n) + { + $vms |? OwnerNode -eq $env:COMPUTERNAME |? { $n -ge [int] ($_.Name -split '-')[-1] } + + LogOutput -IsVb "Starting up to VM $n" + } + # Start subset up to VM #n as defined by %age? + elseif ($pct -ne 100) + { + $n = [int] ($vms.Count * $pct / 100 / $using:numNode) + if ($n -eq 0) { $n = 1 } + + $vms |? OwnerNode -eq $env:COMPUTERNAME |? { $n -ge [int] ($_.Name -split '-')[-1] } + + LogOutput -IsVb ("Starting {0:P1} ({1}) VMs / node" -f ( + ($pct/100), + $n)) + } + # Start all + else + { + $vms |? OwnerNode -eq $env:COMPUTERNAME + }) |? { + + # failed is an unclean offline tbd root causes (can usually be recovered with a restart) + $_.State -eq 'Offline' -or $_.State -eq 'Failed' + + } | Start-ClusterGroup |% { LogOutput "Start $($_.Name)" } + } + } +} + +function Stop-Fleet +{ + [CmdletBinding()] + param( + [Parameter()] + [string] + $Cluster = ".", + + [Parameter()] + [string[]] + $Group = @('*') + ) + + # Parameter set specifying cluster only + $clusterParam = @{} + CopyKeyIf $PSBoundParameters $clusterParam 'Cluster' + + $Node = Get-ClusterNode @clusterParam |? State -eq Up + + # Pause and clear fleet run state so that VMs depart as a coordinated group + Set-FleetPause @clusterParam + Clear-FleetRunState @clusterParam + + Invoke-CommonCommand $Node -InitBlock $CommonFunc -ScriptBlock { + + foreach ($g in $using:Group) + { + # + # Get all non-offline VMs to work on. Note that the State property is dynamically evaluated on reference. + # This list only needs to be acquired once. Note that we do shutdown by resource (the VM) while start + # must go by group to ensure the complete group (including the VM configuration resource) is brought up + # + + $vms = Get-ClusterResource |? OwnerNode -eq $env:COMPUTERNAME |? ResourceType -eq 'Virtual Machine' |? OwnerGroup -like "vm-$g-*" |? State -ne 'Offline' + + try + { + # + # Put all VMs in OfflineAction = Shutdown + # https://docs.microsoft.com/en-us/previous-versions/windows/desktop/mscs/virtual-machines-offlineaction + # + + $vms | Set-ClusterParameter -Name OfflineAction -Value 2 + $vms | Stop-ClusterResource |% { LogOutput "Shutdown $($_.OwnerGroup)" } + + # + # Use TurnOff on remaining nonresponsive VMs + # + + $vms |? State -ne 'Offline' | Set-ClusterParameter -Name OfflineAction -Value 0 + $vms |? State -ne 'Offline' | Stop-ClusterResource |% { LogOutput "TurnOff non-responsive $($_.OwnerGroup)" } + } + finally + { + # + # Restore all VMs to OfflineAction - Save (default) + # + + $vms | Set-ClusterParameter -Name OfflineAction -Value 1 + } + } + } +} + +function Repair-Fleet +{ + [CmdletBinding()] + param ( + [Parameter()] + [string] + $Cluster = '.', + + [Parameter()] + [switch] + $BringUp + ) + + # Parameter set specifying cluster only + $clusterParam = @{} + CopyKeyIf $PSBoundParameters $clusterParam 'Cluster' + + $vms = Get-FleetVM @clusterParam -ControlResponse + + # + # In BringUp mode try to start any group which is Offline + # + + $startVM = @() + if ($BringUp) + { + $startVM = @($vms |? ClusterState -eq 'Offline') + + if ($startVM.Count) + { + $startGroups = foreach ($vm in $startVM) + { + Get-ClusterGroup @clusterParam -Name $vm.Name + } + + $startGroups | Start-ClusterGroup |% { LogOutput "Start $($_.Name)" } + } + + $expectedOffline = 0 + } + + # + # Count offline VMs so that we can check that our actions don't create more. + # + else + { + $expectedOffline = @($vms |? ClusterState -eq 'Offline').Count + } + + # + # Now restart all VMs which are not Offline. This rolls up the VM, heartbeat and control response issues. + # Any offline VMs we decided to start for Bringup are continuing to arrive as this is done. + # + + $repairVM = @($vms |? State -ne Offline |? State -ne Ok) + + # + # If there are no VMs to restart and none that we already started (=> need control checks), done. + # + + if ($repairVM.Count -eq 0 -and $startVM.Count -eq 0) + { + return + } + + if ($repairVM.Count) + { + # + # Get all VM resources to work on. Note that the State property is dynamically evaluated on reference. + # This list only needs to be acquired once. Note that we do shutdown by resource (the VM) while start + # must go by group to ensure the complete group (including the VM configuration resource) is brought up + # + + $vms = Get-ClusterResource @clusterParam |? ResourceType -eq 'Virtual Machine' |? OwnerGroup -in $repairVM.Name + + try + { + # + # Put all VMs in OfflineAction = Shutdown + # https://docs.microsoft.com/en-us/previous-versions/windows/desktop/mscs/virtual-machines-offlineaction + # + + $vms | Set-ClusterParameter -Name OfflineAction -Value 2 + $vms | Stop-ClusterResource |% { LogOutput "Repair Shutdown $($_.OwnerGroup)" } + + # + # Use TurnOff on remaining nonresponsive VMs + # + + $vms |? State -ne 'Offline' | Set-ClusterParameter -Name OfflineAction -Value 0 + $vms |? State -ne 'Offline' | Stop-ClusterResource |% { LogOutput "Repair TurnOff non-responsive $($_.OwnerGroup)" } + } + finally + { + # + # Restore all VMs to OfflineAction - Save (default) + # + + $vms | Set-ClusterParameter -Name OfflineAction -Value 1 + } + + # + # Now get the cluster groups to turn on. + # + + $vms = Get-ClusterGroup @clusterParam |? GroupType -eq 'VirtualMachine' |? Name -in $repairVM.Name + $vms | Start-ClusterGroup |% { LogOutput "Repair Start $($_.Name)" } + } + + # + # Wait for VM hearbeats (NoContact) to arrive. The health check assumes any non-OK lower level state + # is indeed fatal and control response is not needed; we know some VMs are restarting and may not be + # reporting to HyperV yet. Wait for Hyperv. + # + + $await = 5 + do { + + Start-Sleep 10 + $vms = Get-FleetVM + + if (@($vms |? State -eq NoContact).Count -eq 0) + { + break + } + + } while (--$await) + + # + # Now get the full health including control response for final validation + # + + $await = 5 + + do { + + $vms = Get-FleetVM -ControlResponse + + # + # If we've maintained the expected number of offline VMs and the balance are OK, repair has been + # successful. Otherwise throw the assertion. + # + + $nowOffline = @($vms |? ClusterState -eq 'Offline').Count + $nowOk = @($vms |? State -eq 'Ok').Count + + if ($nowOffline -eq $expectedOffline -and + $nowOk -eq ($vms.Count - $expectedOffline)) + { + return + } + + LogOutput "Fleet repair re-probe, pausing 5s" + Start-Sleep 5 + + } while (--$await) + + throw "Fleet repair failed: $($vms.Count) total VMs, $nowOffline offline (expected $expectedOffline), $nowOk Ok (expected $($vms.Count - $expectedOffline))" +} + +function Get-FleetVM +{ + [CmdletBinding()] + param ( + [Parameter()] + [string] + $Cluster = ".", + + [Parameter()] + [string] + $Group = '*', + + [Parameter()] + [switch] + $ControlResponse, + + [Parameter()] + [uint32] + $Timeout = 60 + ) + + # Parameter set specifying cluster only + $clusterParam = @{} + CopyKeyIf $PSBoundParameters $clusterParam 'Cluster' + # check if VMs are created in Arc environment + $arcConfig = Get-ArcConfig -Cluster $Cluster + $arcEnvEnabled = $arcConfig -and $arcConfig.Enabled + $salt = $null + if($arcEnvEnabled) { + $salt = $arcConfig.Salt + } + # + # Start timeout; only takes effect after first control response check. + # + + $t0 = Get-Date + + $clusterView = @{} + $vmView = @{} + Get-ClusterGroup @clusterParam |? GroupType -eq VirtualMachine |? Name -like "vm-$Group-*" |% { $clusterView[$_.Name] = $_ } + Get-VM -CimSession @(Get-ClusterNode @clusterParam |? State -eq Up).Name |? { + if($arcEnvEnabled) + { + $_.Name -like "^vm-$Group-.{1,}-" + $salt + "-\d{3}$" + } + else + { + $_.Name -like "vm-$Group-*" + } + } |% { $vmView[$_.Name] = $_ } + # Union into a composite vm health/state object + # Pass 1 through cluster groups. Pass 2 through VMs to detect those + # not attached as cluster groups (not expected). + # + # Accumulate in hash for return. + + $h = @{} + $ok = 0 + + foreach ($cvm in $clusterView.Values) + { + if ($vmView.Contains($cvm.Name)) { + $vmState = $vmView[$cvm.Name].State + } + else + { + $vmState = 'Unknown' + } + + if ($vmView.Contains($cvm.Name)) { + $vmHeartbeat = $vmView[$cvm.Name].Heartbeat + $vmId = $vmView[$cvm.Name].VMId.Guid + } + else + { + $vmHeartbeat = 'Unknown' + $vmId = $null + } + + # Aggregate state is the union of cluster, vm and heartbeat in that priority order + if ($cvm.State -ne 'Online') + { + $state = $cvm.State + } + elseif ($vmState -ne 'Running') + { + $state = $vmState + } + elseif ($vmHeartbeat -like 'Ok*') + { + $state = 'Ok' + ++$ok + } + else + { + $state = $vmHeartbeat + } + + $h[$cvm.Name] = [pscustomobject] @{ + Name = $cvm.Name + VMId = $vmid + OwnerNode = $cvm.OwnerNode + State = $state + ClusterState = $cvm.State + VMState = $vmState + Heartbeat = $vmHeartbeat + ControlResponse = 'Unknown' + }; + } + + foreach ($vvm in $vmView.Values) + { + # Already visited by cluster view? + if ($clusterView.Contains($vvm.Name)) { continue } + + $h[$vvm.Name] = [pscustomobject] @{ + Name = $vvm.Name + VMId = $vvm.VMId.Guid + OwnerNode = $vvm.ComputerName + State = "NotClustered" + ClusterState = "NotClustered" + VMState = $vvm.State + Heartbeat = $vvm.Heartbeat + ControlResponse = 'Unknown' + }; + } + + # + # If there are no Ok VMs at the higher layer health checks (group, vm, heartbeat), + # no control response work to perform - we alredy know they are all failed. If there + # are OK VMs and we are asked to, then perform the control respone check. The basic + # reason this is opt in is that it *is* currently disruptive to running operations. + # It is conceivable that we should have a way for the control loop to chirp out without + # relying on the pause indication, but deferred for now. + # + + if ($ok -ne 0 -and $ControlResponse) + { + # + # Fill filter hash for all Ok VMs; these are the ones relevant to the + # pause flag live check. + # + + $checkPause = @{} + foreach ($vm in $h.Values) + { + if ($vm.State -eq 'Ok') + { + $checkPause["pause-$($vm.Name)"] = $true + } + } + + $flagPath = Get-FleetPath @clusterParam -PathType Flag + + # + # Force pause response from all VM and wait (under timeout) for the responses. + # Note the residual possibility that a given VM has never paused and will now + # create its pause for the first time - must enumerate on each check. + # + + Set-FleetPause @clusterParam -Force -Timeout 0 + $pauseEpoch = Get-FleetPauseEpoch @clusterParam + + do { + + Start-Sleep -Seconds 1 + + # File -> Value map of responses + $pauseValues = @{} + Get-ChildItem $flagPath\pause-* |? { $checkPause[$_.BaseName] } |% { $pauseValues[$_.BaseName] = Get-Content $_ } + + # If we have responses from all VMs ... + if ($pauseValues.Count -eq $checkPause.Count) + { + # Now group responses - if there is one ... + $pauseGroup = @($pauseValues.Values | Group-Object -AsHashTable) + + if ($pauseGroup.Count -eq 1) + { + # Crack value enumerator to comparable value. + $pauseValue = $pauseGroup.Values |% { $_ } + + # If it is the same as the current epoch, we are done. + if ($pauseValue -eq $pauseEpoch) + { + break + } + } + } + + } while (((Get-Date) - $t0).TotalSeconds -lt $Timeout) + + # + # Now roll the pause values (Ok or leave unmodifed/Unknown) onto the VM control response. + # + + foreach ($k in $pauseValues.Keys) + { + if ($pauseValues[$k] -eq $pauseEpoch) + { + $r = 'Ok' + } + else + { + $r = 'NoControlResponse' + } + + $h[$k -replace 'pause-',''].ControlResponse = $r + } + + # + # ControlResponse overrides Ok on final State response. + # VMs which are already non-Ok continue to carry that indication. + # + + foreach ($k in $h.Keys) + { + if ($h[$k].ControlResponse -ne 'Ok' -and $h[$k].State -eq 'Ok') + { + $h[$k].State = 'NoControlResponse' + } + } + } + + $h.Values +} + +function Show-Fleet +{ + [CmdletBinding()] + param ( + [Parameter()] + [string] + $Cluster = ".", + + [Parameter()] + [string] + $Group = '*' + ) + + # Parameter set specifying cluster only + $clusterParam = @{} + CopyKeyIf $PSBoundParameters $clusterParam 'Cluster' + + $vm = Get-FleetVM @PSBoundParameters + + ########################################################### + write-host -fore green By State + $vm | Group-Object -Property State,ClusterState,VMState,HeartBeat | Sort-Object -Property Name |` + Format-Table -AutoSize -Property @{ Label = 'Count'; Expression = { $_.Count }}, + @{ Label = 'State'; Expression = { $_.Group[0].State }}, + @{ Label = 'ClusterState'; Expression = { $_.Group[0].ClusterState }}, + @{ Label = 'VMState'; Expression = { $_.Group[0].VMState }}, + @{ Label = 'Heartbeat'; Expression = { $_.Group[0].Heartbeat }} + + ########################################################### + write-host -fore green By Host + $vm | Group-Object -Property OwnerNode,State,ClusterState,VMState,HeartBeat | Sort-Object -Property Name |` + Format-Table -AutoSize -Property @{ Label = 'Count'; Expression = { $_.Count }}, + @{ Label = 'OwnerNode'; Expression = { $_.Group[0].OwnerNode }}, + @{ Label = 'State'; Expression = { $_.Group[0].State }}, + @{ Label = 'ClusterState'; Expression = { $_.Group[0].ClusterState }}, + @{ Label = 'VMState'; Expression = { $_.Group[0].VMState }}, + @{ Label = 'Heartbeat'; Expression = { $_.Group[0].Heartbeat }} + + ########################################################### + write-host -fore green By Group + $vm |% { + if ($_.Name -match "^vm-([^-]+)-") { + $_ | Add-Member -NotePropertyName Group -NotePropertyValue $matches[1] -PassThru + } + } | Group-Object -Property Group,State,ClusterState,VMState,HeartBeat| Sort-Object -Property Name | ` + Format-Table -AutoSize -Property @{ Label = 'Count'; Expression = { $_.Count }}, + @{ Label = 'Group'; Expression = { $_.Group[0].Group }}, + @{ Label = 'State'; Expression = { $_.Group[0].State }}, + @{ Label = 'ClusterState'; Expression = { $_.Group[0].ClusterState }}, + @{ Label = 'VMState'; Expression = { $_.Group[0].VMState }}, + @{ Label = 'Heartbeat'; Expression = { $_.Group[0].Heartbeat }} + + ########################################################### + write-host -fore green By IOPS + # build 5 orders of log steps + $logstep = 10,20,50 + $log = 1 + $logs = 1..5 |% { + + $logstep |% { $_ * $log } + $log *= 10 + } + + # build log step names; 0 is the > range catchall + $lognames = @{} + foreach ($step in $logs) { + + if ($step -eq $logs[0]) { + $lognames[$step] = "< $step" + } else { + $lognames[$step] = "$pstep - $($step - 1)" + } + $pstep = $step + } + $lognames[0] = "> $($logs[-1])" + + # now bucket up VMs by flow rates + $qosbuckets = @{} + $qosbuckets[0] = 0 + $logs |% { + $qosbuckets[$_] = 0 + } + + $node = Get-AccessNode @clusterParam + + # Note: group all vm disks together and bucket sum of IOPS + Get-StorageQoSFlow -CimSession $node | Group-Object -Property InitiatorName |% { + + $initiatorIops = ($_.Group | Measure-Object -Sum InitiatorIops).Sum + $found = $false + foreach ($step in $logs) { + + if ($initiatorIops -lt $step) { + $qosbuckets[$step] += 1; + $found = $true + break + } + } + # if not bucketed, it is greater than range + if (-not $found) { + $qosbuckets[0] += 1 + } + } + + # find min/max buckets with nonzero counts, by $logs index + # this lets us present a continuous range, with interleaved zeroes + $bmax = -1 + $bmin = -1 + foreach ($i in 0..($logs.Count - 1)) { + if ($qosbuckets[$logs[$i]]) { + + if ($bmin -lt 0) { + $bmin = $i + } + $bmax = $i + } + } + # raise max if we have > range + if ($qosbuckets[0]) { + $bmax = $logs.Count - 1 + } + $range = @($logs[$bmin..$bmax]) + # add > range if needed, at end + if ($qosbuckets[0]) { + $range += 0 + } + + $(foreach ($i in $range) { + + New-Object -TypeName psobject -Property @{ + Count = $qosbuckets[$i]; + IOPS = $lognames[$i] + } + }) | Format-Table Count,IOPS -AutoSize +} + +function Move-Fleet +{ + [CmdletBinding(DefaultParameterSetName = "Default")] + param( + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'ByPercent')] + [string] + $Cluster = ".", + + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'ByPercent')] + [switch] + $KeepCSV, + + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'ByPercent')] + [switch] + $KeepVM, + + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'ByPercent')] + [switch] + $ShiftCSV, + + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'ByPercent')] + [ValidateRange(0,100)] + [uint32] + $DistributeVMPercent = 0, + + [Parameter(ParameterSetName = 'ByPercent', Mandatory = $true)] + [ValidateRange(1,100)] + [uint32] + $VMPercent = 100, + + [Parameter(ParameterSetName = 'Default')] + [switch] + $Running + ) + + # Parameter set specifying cluster only + $clusterParam = @{} + CopyKeyIf $PSBoundParameters $clusterParam 'Cluster' + + # Note that the collect volume is never affected by fleet CSV movements + $nodes = @(Get-ClusterNode @clusterParam | Sort-Object -Property Name) + $nodesLive = @($nodes |? State -eq Up) + + if ($nodes.Count -le 1 -or $nodesLive.Count -le 1) + { + Write-Error "Cluster only contains a single node ($($nodes.Count)) or less than two live nodes ($($nodesLive.Count) - movement not possible" + return + } + + $csv = GetMappedCSV @clusterParam |? VDName -ne $collectVolumeName + + if ($ShiftCSV) + { + # Shift rotates all csv's node ownership by one node, in lexical order of + # current node owner name. This is useful for forcing all-out-of-position ops + # quickly without VM movement. + LogOutput "Shifting CSV owners" + + # CSV object's OwnerNode is dynamically computed. Build a hash of new OwnerNodes + # for the CSV in pass 1 and move in pass 2. + $moveh = @{} + foreach ($n in 0..($nodes.Length - 1)) + { + $moveTo = ($n + 1) % $nodes.Length + $moveh[$nodes[$moveTo].Name] = $csv |? { $_.OwnerNode.Name -eq $nodes[$n].Name } + } + + foreach ($n in $moveh.Keys) + { + $moveh[$n] | Move-ClusterSharedVolume $n @clusterParam + } + } + + # Re-home all CSV unless declined + elseif (-not $KeepCSV) + { + LogOutput "Aligning CSVs" + + # move all csvs named by node names back to their named node + $nodes |% { + $node = $_ + $csv |? VDName -match "$($node.Name)(?:-.+){0,1}" |? OwnerNode -ne $node.Name |% { $_ | Move-ClusterSharedVolume $node.Name @clusterParam } + } + } + + if (-not $KeepVM) + { + if ($DistributeVMPercent -gt 0) + { + LogOutput "Distributing VMs ($DistributeVMPercent%)" + } + else + { + LogOutput "Aligning VMs" + } + + # + # Build aligned ownership lists in order of node list, in lexical name + # order to take advantage of the stable distribution shift. Shifting by 60% + # will build on the moves for 50%, and so forth. + # + + $vms = Get-ClusterGroup @clusterParam |? GroupType -eq VirtualMachine + + # + # Only rotate online VMs? This allows one model of composing partial startup with rotation - start + # given number in-place and apply rotation to those. + # + + if ($Running) + { + $vms = $vms |? State -eq Online + + if ($vms.Count -eq 0) + { + Write-Error "No VMs currently running, no rotation performed" + } + } + + $groups = [collections.arraylist]@() + $count = 0 + foreach ($node in $nodes) + { + $arr = [collections.arraylist]@() + foreach ($vm in $vms | Sort-Object -Property Name) + { + if ($vm.Name -match "vm-.*-$($node.Name).*-\d+$") + { + $null = $arr.Add($vm) + } + } + if ($count -eq 0) + { + $count = $arr.Count + } + elseif ($count -ne $arr.Count) + { + Write-Error "Node $node only has $($arr.Count) aligned VMs created for it. Please check that all nodes had the same number of VMs created (New-Fleet -VMs)." + } + $null = $groups.Add($arr) + } + + if($VMPercent -lt 100) + { + # + # How many VMs per node to consider in the rotation (partial start cases, without -Running). + # Discard upper portion of VMs/node. Note this is/must be consistent with start-fleet, + # get-fleetdatadiskestimate and other vm-percentage calculations. + # + + $n = [int] ($VMPercent * $groups[0].Count /100 ) + if ($n -eq 0) { $n = 1 } + + # No trim if rounding took it back to 100%. + + if ($n -ne $groups[0].Count) + { + foreach ($g in $groups) + { + $g.RemoveRange($n, $g.Count - $n) + } + } + } + + if ($DistributeVMPercent -gt 0) + { + # + # How many VMs to distribute (at least 1 VM/node) + # + + $n = [int] ($DistributeVMPercent * $groups[0].Count / 100) + if ($n -eq 0) { $n = 1 } + + $groups = GetDistributedShift -Group $groups -N $n + } + + try + { + # + # Strip out VMs already in the correct location and farm out movement + # to the respective host node. + # + + $j = @() + for ($i = 0; $i -lt $groups.Count; ++$i) + { + $moveList = @(foreach ($vm in $groups[$i]) + { + if ($vm.OwnerNode -ne $nodes[$i]) + { + $vm + } + }) + + if ($moveList.Count -eq 0) + { + continue + } + + $target = $nodes[$i].Name + LogOutput Moving $moveList.Count VMs to $target + + # + # Carefully quote the movelist so that it passes as a single parameter (list of a single element, a list) + # + + $j += Invoke-CommonCommand -AsJob -InitBlock $CommonFunc -ArgumentList @(,$moveList) -ScriptBlock { + + [CmdletBinding()] + param( + [object[]] + $vms + ) + + foreach ($vm in $vms) + { + LogOutput "Moving $($vm.Name): $($vm.OwnerNode) -> $using:target" + + $expectedState = $vm.State + + try + { + # The default move type is live, but live does not degenerately handle offline vms yet. + # Discard the warning stream - if an issue occurs the cluster will offer a generic comment + # about credential remoting (in addition to the error) which is not going to be relevant in + # our cases. + if ($vm.State -eq 'Offline') + { + $null = Move-ClusterVirtualMachineRole @using:clusterParam -Name $vm.Name -Node $using:target -MigrationType Quick -ErrorAction Stop 3> $null + } + else + { + $null = Move-ClusterVirtualMachineRole @using:clusterParam -Name $vm.Name -Node $using:target -ErrorAction Stop 3> $null + } + } + catch + { + # + # Workaround false failure of VM quick/offline migration - sink the error if VM appears to land, otherwise propagate it. + # + + $checkVM = Get-ClusterGroup @using:clusterParam -Name $vm.Name + + $await = 0 + if ($expectedState -eq 'Offline') + { + $await = 5 + do { + + if ($checkVM.State -eq $expectedState -and $checkVM.OwnerNode -eq $using:target) + { + LogOutput "Verified VM quick migration on retry $(5 - $await + 1) $($vm.Name) -> $using:target" + break + } + + if (-not --$await) + { + break + } + + Start-Sleep 1 + $checkVM = Get-ClusterGroup @using:clusterParam -Name $vm.Name + + } while ($true) + } + + if ($await -eq 0) + { + if ($checkVM.State -ne $expectedState) { LogOutput "Verify $($vm.Name) $($checkVM.State) != $expectedState" } + if ($checkVM.OwnerNode -ne $using:target) { LogOutput "Verify $($vm.Name) $($checkVM.OwnerNode) != $($using:target)" } + LogOutput -ForegroundColor Red "Error moving $($vm.Name)" + + $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId ` + -ErrorMessage $null ` + -ErrorCategory $_.CategoryInfo.Category ` + -Exception $_.Exception ` + -TargetObject $_.TargetObject + + $psCmdlet.WriteError($errorObject) + } + } + } + } + } + + # + # And await completion (errors flow through) + # + + $jobErrors = @() + while ($true) + { + $done = @($j | Wait-Job -Timeout 1) + try { + $j | Receive-Job + } catch { + $jobErrors += $_ + } + if ($done.Count -eq $j.Count) + { + break + } + } + } + finally + { + $j | RemoveCommonJobSession + $j | Remove-Job + } + + if ($jobErrors.Count) + { + $jobErrors |% { Write-Error -ErrorRecord $_ } + } + } +} + +function Set-FleetPowerScheme +{ + param ( + [Parameter()] + [string] + $Cluster = ".", + + [Parameter(Mandatory = $true)] + [ValidateSet("Balanced","HighPerformance","PowerSaver")] + [string] + $Scheme + ) + + $node = Get-ClusterNode -Cluster $Cluster |? State -eq Up + + switch ($Scheme) + { + "Balanced" { $guid = '381b4222-f694-41f0-9685-ff5bb260df2e' } + "HighPerformance" { $guid = '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' } + "PowerSaver" { $guid = 'a1841308-3541-4fab-bc81-f71556f20b4a' } + } + + Invoke-CommonCommand -ComputerName $node -ScriptBlock { + powercfg /setactive $using:guid + } +} + +function Get-FleetPowerScheme +{ + param ( + [Parameter()] + [string] + $Cluster = "." + ) + + $node = Get-ClusterNode -Cluster $Cluster |? State -eq Up + + $scheme = Invoke-CommonCommand -ComputerName $node -ScriptBlock { + powercfg /getactivescheme + } + + # Map/Normalize names off of GUID (i8ln?) + $normalScheme = foreach ($s in $scheme) + { + $null = $s -match 'GUID:\s+(\S+)' + + switch ($matches[1]) + { + '381b4222-f694-41f0-9685-ff5bb260df2e' { $name = "Balanced" } + '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' { $name = "HighPerformance" } + 'a1841308-3541-4fab-bc81-f71556f20b4a' { $name = "PowerSaver" } + + default { + Write-Error "Unnown running power scheme guid $($matches[1]) - please provide feedback to VM Fleet and normalize to known (Balanced, High Performance or Power Saver - see powercfg /list)" + return + } + } + + [pscustomobject] @{ Name = $name; Node = $s.PSComputerName } + } + + $g = @($normalScheme | Group-Object -Property Name ) + + if ($g.Count -eq 1) + { + return $g[0].Name + } + + foreach ($s in $normalScheme) + { + Write-Warning "Node $($s.Node): $($s.Name)" + } + + Write-Error "Inconsistent power schemes running in cluster - please verify intended configuration" +} + +function Set-FleetQoS +{ + # apply given QoS policy (by name) to all VMs on specified nodes + [CmdletBinding(DefaultParameterSetName = "ByCluster")] + param ( + [Parameter(ParameterSetName = "ByCluster")] + [string] + $Cluster = ".", + + [Parameter(ParameterSetName = "ByNode", Mandatory = $true)] + [string[]] + $Node, + + [Parameter()] + [string] + $Name + ) + + if ($PSCmdlet.ParameterSetName -eq 'ByCluster') + { + $Node = Get-ClusterNode -Cluster $Cluster |? State -eq Up + } + + if ($PSBoundParameters.ContainsKey('Name')) + { + # QoS policy must exist, else error out + $qosp = Get-StorageQoSPolicy -Name $Name -CimSession $Node[0] + if ($null -eq $qosp) + { + # cmdlet error sufficient + return + } + + $id = $qosp.PolicyId + LogOutput -IsVb "applying QoS Policy: $Name (PolicyID $id)" + } + else + { + # clears QoS policy + $id = $null + LogOutput -IsVb "removing QoS Policy" + } + + Invoke-Command $Node { + + Get-VM |% { Get-VMHardDiskDrive $_ |% { Set-VMHardDiskDrive -QoSPolicyID $using:id -VMHardDiskDrive $_ }} + } +} + +# +# Dynamic ValidateSet should be used when available - not present in inbox PS5.1 +# This would replace the large ValidateSet with a simple [ValidateSet([VMSpec])] +# + +# class VMSpec : System.Management.Automation.IValidateSetValuesGenerator { +class VMSpec { + + [int] $ProcessorCount + [int64] $MemoryStartupBytes + [string] $Alias + + VMSpec( + [int] + $ProcessorCount, + + [int64] + $MemoryStartupBytes, + + [string] + $Alias + ) + { + $this.ProcessorCount = $ProcessorCount + $this.MemoryStartupBytes = $MemoryStartupBytes + $this.Alias = $Alias + } + + <# + [String[]] GetValidValues() { + + return $script:VMSpecs.Keys + } + #> +} + +$VMSpecs = @{} +$VMSpecs['A1v2'] = [VMSpec]::new(1, 2GB, $null) +$VMSpecs['A2v2'] = [VMSpec]::new(2, 4GB, $null) +$VMSpecs['A4v2'] = [VMSpec]::new(4, 8GB, $null) +$VMSpecs['A8v2'] = [VMSpec]::new(8, 16GB, $null) + +$VMSpecs['A2mv2'] = [VMSpec]::new(2, 16GB, $null) +$VMSpecs['A4mv2'] = [VMSpec]::new(4, 32GB, $null) +$VMSpecs['A8mv2'] = [VMSpec]::new(8, 64GB, $null) + +# example of an alias when vcpu/mem are the same +# $VMSpecs['D1v2'] = [VMSpec]::new(0, 0, 'A1v2') + +#> + +function Get-FleetComputeTemplate +{ + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [ValidateSet( + 'A1v2','A2mv2','A2v2','A4mv2','A4v2','A8mv2','A8v2' + )] + [string] + $ComputeTemplate + ) + + $spec = $VMSpecs[$ComputeTemplate] + if ($spec.ProcessorCount -eq 0) { + $spec = $VMSpec[$spec.Alias] + } + + $spec +} + +function Set-Fleet +{ + [CmdletBinding(DefaultParameterSetName = "FullSpecStatic")] + param( + [Parameter(ParameterSetName = 'DataDisk')] + [Parameter(ParameterSetName = 'FullSpecDynamic')] + [Parameter(ParameterSetName = 'FullSpecStatic')] + [string] + $Cluster = ".", + + [Parameter(ParameterSetName = 'FullSpecDynamic', Mandatory = $true)] + [Parameter(ParameterSetName = 'FullSpecStatic', Mandatory = $true)] + [int] + $ProcessorCount, + + [Parameter(ParameterSetName = 'FullSpecDynamic', Mandatory = $true)] + [Parameter(ParameterSetName = 'FullSpecStatic', Mandatory = $true)] + [int64] + $MemoryStartupBytes, + + [Parameter(ParameterSetName = 'FullSpecDynamic', Mandatory = $true)] + [int64] + $MemoryMaximumBytes, + + [Parameter(ParameterSetName = 'FullSpecDynamic', Mandatory = $true)] + [int64] + $MemoryMinimumBytes, + + # Note: ValidateSet MUST be kept in line with definitions. + # This can become dyanmic if/when we can move forward to PS 6.1/later + # where the dynamic ValidateSet capability can be used. + [Parameter(ParameterSetName = 'SizeSpec')] + [ValidateSet( + 'A1v2','A2mv2','A2v2','A4mv2','A4v2','A8mv2','A8v2' + )] + [string] + $ComputeTemplate = 'A1v2', + + [Parameter(ParameterSetName = 'DataDisk', Mandatory = $true)] + [uint64] + $DataDiskSize, + + [Parameter(ParameterSetName = 'DataDisk')] + [switch] + $Running + ) + + $nodes = @(Get-ClusterNode -Cluster $Cluster |? State -eq Up) + # check if VMs are created in Arc environment + $arcConfig = Get-ArcConfig -Cluster $Cluster + $arcEnvEnabled = $arcConfig -and $arcConfig.Enabled + $resourceGroup, $salt = $null + if($arcEnvEnabled) { + $resourceGroup = $arcConfig.ResourceGroup + $salt = $arcConfig.Salt + } + # Fill parameters + $p = @{} + if($arcEnvEnabled){ + $p['ArcEnvEnabled'] = $arcEnvEnabled + $p['ResourceGroup'] = $resourceGroup + } + switch ($PSCmdlet.ParameterSetName) { + + 'FullSpecDynamic' + { + $p['DynamicMemory'] = $true + + $p['ProcessorCount'] = $ProcessorCount + $p['MemoryMaximumBytes'] = $MemoryMaximumBytes + $p['MemoryMinimumBytes'] = $MemoryMinimumBytes + $p['MemoryStartupBytes'] = $MemoryStartupBytes + + LogOutput -IsVb "DynamicMemory: applying ProcessorCount $ProcessorCount Memory[ Start $('{0:N2}' -f ($MemoryStartupBytes/1GB))GB : $('{0:N2}' -f ($MemoryMinimumBytes/1GB))GB - $('{0:N2}' -f ($MemoryMaximumBytes/1GB))GB ]" + } + + 'FullSpecStatic' + { + $p['StaticMemory'] = $true + + $p['ProcessorCount'] = $ProcessorCount + $p['MemoryStartupBytes'] = $MemoryStartupBytes + + LogOutput -IsVb "StaticMemory: applying ProcessorCount $ProcessorCount Memory[ $('{0:N2}' -f ($MemoryStartupBytes/1GB))GB ]" + } + + 'SizeSpec' + { + $spec = Get-FleetComputeTemplate -ComputeTemplate $ComputeTemplate + + $p['ProcessorCount'] = $spec.ProcessorCount + $p['StaticMemory'] = $true + $p['MemoryStartupBytes'] = $spec.MemoryStartupBytes + + LogOutput -IsVb "StaticMemory: applying $ComputeTemplate compute template: ProcessorCount $($spec.ProcessorCount) Memory[ $('{0:N2}' -f ($spec.MemoryStartupBytes/1GB))GB ]" + } + } + + if ($PSCmdlet.ParameterSetName -ne 'DataDisk') + { + Invoke-Command $nodes -ArgumentList $p { + + param($p) + + foreach ($vm in Get-VM) + { + # Match off home node name from VM. This identifies the VM directory location. + if ($vm.Name -notmatch 'vm-[^-]+-(.*)-(\d+)') + { + # not a fleet VM + continue + } + + if ($vm.State -ne 'Off') + { + Write-Error "Cannot set VM properties on VMs which are not offline: $($vm.Name) ($($vm.State))" + } + else + { + if($p['ArcEnvEnabled']){ + $name = $vm.Name + $cpuCount = $p['ProcessorCount'] + $memorymb = $p['MemoryStartupBytes']/1048576 + $rg = $p['ResourceGroup'] + # for now, this script only supports static memory for Arc vms + az stack-hci-vm update --name $name --v-cpus-available $cpuCount --memory-mb $memorymb --resource-group $rg + # currently, vms go back to running state instead of original state before peforming an update + az stack-hci-vm stop --name $name --resource-group $rg + } + else { + $vm | Set-VM @p + } + + continue + } + } + } + } + else + { + # + # This needs to run without remoting. The cluster cmdlets embedded within the HyperV set are not reliably + # executable in remote sessions due to credential delegation when not using CredSSP. Specifically, + # Update-ClusterVirtualMachineConfiguration is invoked to update the clustered role when disks are modifieed. + # + + $vms = @(Get-VM -CimSession $nodes.Name |? { + if($arcEnvEnabled) { + $_.Name -match "^vm-.{1,}-" + $salt + "-\d{3}$" + } + else { + $_.Name -like 'vm-*' + } + } + ) + for ($i = 0; $i -lt $vms.Count; ++$i) + { + $vm = $vms[$i] + + $pcomplete = [int](100*$i/($vms.Count)) + Write-Progress -Id 0 -Activity "Updating VM Data Disks" -PercentComplete (100*$i/($vms.Count)) -CurrentOperation $vm.Name -Status "$pcomplete% Complete:" + + # Skip non-running VMs? + if ($Running -and $vm.State -ne 'Running') + { + continue + } + # Explicitly work on non-running VMs? Skip running VMs. + elseif ($PSBoundParameters.ContainsKey('Running') -and -not $Running -and $vm.State -eq 'Running') + { + continue + } + + # + # ControllerNumber/Location 0/0 is the boot/OS VHD - that is not modified. + # Order the list so that the boot/OS VHD will always be the first, data the second. + # + + $vhd = @($vm | Get-VMHardDiskDrive | Sort-Object -Property ControllerNumber,ControllerLocation) + + if ($vhd.Count) + { + # Only expect exactly one or zero data disks at this time. Do not operate if additional + # devices have been added. + if ($vhd.Count -gt 2) + { + Write-Error "Unexpected number of VM hard disks ($($vhd.Count)) attached to VM $($vm.Name): expecting at most a single data disk" + continue + } + + function Remove + { + Write-Progress -Id 1 -ParentId 0 -Activity "Detaching VM Data Disk from VM" + $path = $vhd[1].Path + $vhd[1] | Remove-VMHardDiskDrive + Write-Progress -Id 1 -ParentId 0 -Activity "Removing VM Data Disk" + Remove-Item $path -Force + Write-Progress -Id 1 -ParentId 0 -Activity "Removing VM Data Disk" -Completed + } + + function Create + { + if($arcEnvEnabled){ + $vmName = $vm.Name + $dataDiskName = "data-$vmName.vhdx" + $path = Join-Path $vm.Path $dataDiskName + } + else { + $path = Join-Path $vm.Path "data.vhdx" + } + + if ($null -eq $path) + { + continue + } + + # Scrub stale/detached vhdx + if (Test-Path $path) + { + Remove-Item $path -Force + } + + Write-Progress -Id 1 -ParentId 0 -Activity "Creating VM Data Disk" + # Allow VHD objects for newly created VHDX to appear on the pipeline. Note that HCI is 4KN, do not create + # the default 512e layout. + New-VHD -Path $path -SizeBytes $DataDiskSize -Fixed -LogicalSectorSize 4096 + + Write-Progress -Id 1 -ParentId 0 -Activity "Extending VM Data Disk Valid Data Length" + # Push valid data - this could be parameterized to expose the true fresh-out-of-box + # case of an empty/sparsed VHDX. This does not replace pre-seasoning though it does + # eliminate the first level of FS effects. + # Redirect stdout, allow stderr to flow out. + fsutil file setvaliddata $path (Get-Item $path).Length > $null + + Write-Progress -Id 1 -ParentId 0 -Activity "Creating VM Data Disk" -Completed + $vm | Add-VMHardDiskDrive -Path $path + } + + # Remove? + if ($DataDiskSize -eq 0) + { + # No data device? + if ($vhd.Count -eq 1) + { + LogOutput "Confirmed no data disk attached to $($vm.Name)" + continue + } + + LogOutput "Removing data disk from $($vm.Name)" + Remove + } + + # Create data VHD? + elseif ($vhd.Count -eq 1) + { + LogOutput "Creating data disk for $($vm.Name)" + Create + } + + # Resize existing data VHD. Note that this is destructive - we do not attempt to manage + # a first-class resize up/down of the disk and its contained partitions/filesystems. + # We assume for now that any disk level initialization will be repeated by the calling + # framework, as notified by new vhd objects being returned in the pipeline. + else + { + $vhdx = Get-VHD -ComputerName $vm.ComputerName -VMId $vm.Id |? Path -eq $vhd[1].Path + if ($null -eq $vhdx) + { + Write-Error "Could not find VHDX on VM ($vm.Name) corresponding to $($vhd[1].Path)" + } + elseif ($vhdx.Size -ne $DataDiskSize) + { + # Native Resize-VHD does not apply to fixed VHDX - Remove/Create new + LogOutput "Resizing (remove/create) data disk for $($vm.Name)" + Remove + Create + } + } + } + } + + Write-Progress -Id 0 -Activity "Updating VM Data Disks" -Completed + } +} + +function Get-FleetDisk +{ + [CmdletBinding()] + param( + [Parameter()] + [string] + $Cluster = '.', + + [Parameter()] + [switch] + $OsDisk, + + [Parameter()] + [switch] + $All + ) + + $nodes = @(Get-ClusterNode -Cluster $Cluster |? State -eq Up) + foreach ($vhd in Get-VM -CimSession $nodes.Name |? Name -like 'vm-*' | Get-VMHardDiskDrive) + { + # If not including OS disk, only emit non-boot devices (boot is always at 0/0) + if ($All -or + ($OsDisk -and ($vhd.ControllerLocation -eq 0 -and $vhd.ControllerNumber -eq 0)) -or + (-not $OsDisk -and ($vhd.ControllerLocation -ne 0 -or $vhd.ControllerNumber -ne 0))) + { + $vhd + } + } +} + +function Get-FleetVolumeEstimate +{ + [CmdletBinding()] + param( + [Parameter()] + [string] + $Cluster = '.', + + [Parameter()] + [uint64] + $CollectVolumeSize = (200GB), + + [Parameter()] + [ValidateRange(1,50)] + [uint32] + $MAPMirrorPercentage = 20 + ) + + $pool = @(Get-StorageSubsystem -CimSession $Cluster |? AutomaticClusteringEnabled | Get-StoragePool |? IsPrimordial -eq $false) + + if ($pool.Count -eq 0) + { + Write-Error "No pool found" + return + } + + if ($pool.Count -ne 1) + { + Write-Error "Unexpected number of pools found ($($pool.Count)) - please verify environment configuration" + return + } + + $pool = $pool[0] + $poolFree = $pool.Size - $pool.AllocatedSize + + # + # Get relevant m/p storage tiers. Subtle point - 2019 documented creating nested tiers manually w/o On. + # So we search for NestedMirror* (w/wo mediatype) and *Parity*, the latter so we get [Nested]ParityOn as well + # as NestedParity. Since we demand only one matching we're safe against odd cases. + # + + $tierM = @($pool | Get-StorageTier |? ResiliencySettingName -eq Mirror |? FriendlyName -like 'MirrorOn*') + $tierNM = @($pool | Get-StorageTier |? ResiliencySettingName -eq Mirror |? FriendlyName -like 'NestedMirror*') + $tierP = @($pool | Get-StorageTier |? ResiliencySettingName -eq Parity |? FriendlyName -like '*Parity*') + + if ($tierM.Count -eq 0) + { + Write-Error "No per-media type mirror resiliency tier (MirrorOn) found for storage pool `'$($pool.FriendlyName)`' - please verify environment configuration" + return + } + + if ($tierM.Count -gt 1) + { + Write-Error "Multiple per-media type mirror resiliency tiers (MirrorOn) found for storage pool `'$($pool.FriendlyName)`' - estimation not supported" + return + } + else + { + $tierM = $tierM[0] + } + + if ($tierNM.Count -gt 1) + { + Write-Error "Multiple per-media type nested mirror resiliency tiers (NestedMirrorOn) found for storage pool `'$($pool.FriendlyName)`' - estimation not supported" + return + } + elseif ($tierNM.Count -ne 0) + { + $tierNM = $tierNM[0] + } + else + { + $tierNM = $null + } + + if ($tierP.Count -gt 1) + { + Write-Error "Multiple per-media type parity resiliency tiers (MirrorOn) found for storage pool `'$($pool.FriendlyName)`' - estimation not supported" + } + + if ($tierP.Count -eq 0) + { + Write-Warning "No per-media type parity resiliency tier (ParityOn) found for storage pool `'$($pool.FriendlyName)`' - parity estimation skipped" + $tierP = $null + } + else + { + $tierP = $tierP[0] + } + + # Mirror efficiency is simply number of copies. + $pEff = 0 + $mEff = 1 / $tierM.NumberOfDataCopies + if ($null -ne $tierNM) { $nmEff = 1 / $tierNM.NumberOfDataCopies } + + if ($null -ne $tierP) + { + # Estimate parity tier efficiency based on reported max v. free space. + $tierPEst = Get-StorageTierSupportedSize -InputObject $tierP + $pEff = $tierPEst.TierSizeMax / $poolFree + } + + # Determine reserve capacity per default guidance - one capacity device per node up to 4 total in cluster. + # For estimation take the largest such device - if there is variance it should be within a very narrow + # band (e.g., all 10TB devices plus or minus some small amount). + $nodeCount = @(Get-ClusterNode -Cluster $Cluster).Count + $capDevice = $pool | Get-PhysicalDisk -CimSession $Cluster |? Usage -eq Auto-Select | Sort-Object -Property Size -Descending | Select-Object -First 1 + + if ($null -eq $capDevice) + { + Write-Error "Could not determine capacity device size for storage pool `'$($pool.FriendlyName)`' - please verify environment configuration and validate that there are Auto-Select devices reported" + } + + if ($nodeCount -gt 4) + { + $capReserve = $capDevice.Size * 4 + } + else + { + $capReserve = $capDevice.Size * $nodeCount + } + + LogOutput -IsVb ("Pool {0:0.00}TiB total {1:0.00}TiB free: {2:0.00}TiB capacity devices in {3:0} node cluster => {4:0.00}TiB rebuild reserve" -f ( + ($pool.Size/1TB), + ($poolFree/1TB), + ($capDevice.Size/1TB), + ($nodeCount), + ($capReserve/1TB))) + + $nmVb = $null + if ($null -ne $tierNM) { $nmVb = ", {0:P1} nested mirror efficiency" -f $nmEff } + + LogOutput -IsVb ("Using {0:P1} mirror efficiency$nmVb, {1:P1} parity efficiency and {2}:{3} MAP mirror:parity ratio" -f ( + $mEff, + $pEff, + $MAPMirrorPercentage, + (100-$MAPMirrorPercentage))) + + # + # Now roll up to the usable free space in the pool. This is the actual (current) free space plus the space + # taken by existing (if any) non-infrastructure volumes, the only one of which is ClusterPerformanceHistory. + # + + $collect = $null + + # Add back the footprint of existing infra volumes (if any). It might be best to validate that all of the + # ones other than (potentiall existing) collect do indeed match to nodes per the fleet naming convention, + # but there should be no other volumes present. + $pool | Get-VirtualDisk |? FriendlyName -notin @('ClusterPerformanceHistory') |% { + + $poolFree += $_.FootprintOnPool + if ($_.FriendlyName -eq 'collect') + { + $collect = $_ + } + } + + # If an explicit collect size is provided, use it. + # If the collect volume already exists, warn if the provided (if any) collect size estimate does not match. + # If an explicit collect size is not provided, use the existing volume if present. + if ($PSBoundParameters.ContainsKey('CollectVolumeSize')) + { + if ($null -ne $collect -and + $CollectVolumeSize -ne $collect.Size) + { + Write-Warning ("A collect volume already exists but is not the same size as specified - {0:0.00}GiB != {1:0.00}GiB. The estimates will assume this is the desired size and that the existing volume would be deleted and recreated following the new specification. If this is not the intent, rerun without specifying the size to base the estimates on the existing volume." -f ( + ($collect.Size/1GB), + ($CollectVolumeSize/1GB))) + } + + # Per spec; existing volume (if any) was already accounted in usable space + LogOutput -IsVb ("Collect: volume size specified is {0:0.00}GiB" -f ($CollectVolumeSize/1GB)) + $poolFree -= $CollectVolumeSize / $mEff + } + else + { + if ($null -ne $collect) + { + # Use existing volume; take it back out of usable space + LogOutput -IsVb ("Collect: using existing volume ({0:0.00}GiB)" -f ($collect.Size/1GB)) + $poolFree -= $collect.FootprintOnPool + } + else + { + # Per default spec + LogOutput -IsVb ("Collect: using default volume size ({0:0.00}GiB)" -f ($CollectVolumeSize/1GB)) + $poolFree -= $CollectVolumeSize / $mEff + } + } + + # Finally, remove the repair reserve + $poolFree -= $capReserve + + # + # Now estimate mirror and MAP volume tier sizes to arrive within a near margin of the usable free space. + # + + function RoundSize + { + param( [uint64] $size ) + + # Basic precision constrol: + # Truncate TiB/GiB to nearest 10GiB, nearest 5GiB if < 500GiB + $sizeGB = [uint32] ($size/1GB) + if ($sizeGB -lt 500) + { + $sizeGB -= $sizeGB % 5 + } + else + { + $sizeGB -= $sizeGB % 10 + } + + return $sizeGB * 1GB + } + + # Collect volume estimate, if it does not already exist or an overriding size was specified. + if ($null -eq $collect -or $PSBoundParameters.ContainsKey('CollectVolumeSize')) + { + [VolumeEstimate]::new([VolumeType]::Collect, $CollectVolumeSize, $tierm.FriendlyName, 0, $null) + } + + # Mirror + $vsizeM = RoundSize (($poolFree * $mEff)/$nodeCount) + [VolumeEstimate]::new([VolumeType]::Mirror, $vsizeM, $tierM.FriendlyName, 0, $null) + + # Nested Mirror? + if ($null -ne $tierNM) + { + $vsizeM = RoundSize (($poolFree * $nmEff)/$nodeCount) + [VolumeEstimate]::new([VolumeType]::NestedMirror, $vsizeM, $tierNM.FriendlyName, 0, $null) + } + + if ($null -ne $tierP) + { + # MAP + # This is derived by solving the system of equations + # p/peff + m/meff = poolFree + # p/m = the given ratio of parity to mirror capacity + # Note we can substitute for p (or m) in terms of the ratio; the solution + # flows from there. MAP on environments with nested mirror defined are + # presumed to use that tier definition for MAP. + + if ($null -ne $tierNM) + { + $mEffToUse = $nmEff + $mTierName = $tierNM.FriendlyName + } + else + { + $mEffToUse = $mEff + $mTierName = $tierM.FriendlyName + } + + $pmRatio = (100 - $MAPMirrorPercentage)/$MAPMirrorPercentage + $vsizeM = [uint64] ($poolFree / ($pmRatio/$pEff + 1/$mEffToUse)) + $vsizeP = RoundSize ($pmRatio * $vsizeM / $nodeCount) + $vsizeM = RoundSize ($vsizeM / $nodeCount) + [VolumeEstimate]::new([VolumeType]::MAP, $vsizeM, $mTierName, $vsizeP, $tierP.FriendlyName) + } +} + +function Get-FleetDataDiskEstimate +{ + [CmdletBinding(DefaultParameterSetName = 'Default')] + param( + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'ByPercent')] + [Parameter(ParameterSetName = 'ByVMs')] + [string] + $Cluster = '.', + + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'ByPercent')] + [Parameter(ParameterSetName = 'ByVMs')] + [double] + [ValidateRange(10,1000)] + $CachePercent, + + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'ByPercent')] + [Parameter(ParameterSetName = 'ByVMs')] + [ValidateRange(1,100)] + [double] + $CapacityPercent, + + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'ByPercent')] + [Parameter(ParameterSetName = 'ByVMs')] + [switch] + $Higher, + + [Parameter(ParameterSetName = 'ByVMs', Mandatory = $true)] + [uint32] + $VMs, + + [Parameter(ParameterSetName = 'Default')] + [switch] + $Running, + + [Parameter(ParameterSetName = 'ByPercent', Mandatory = $true)] + [ValidateRange(1,100)] + [double] + $VMPercent + ) + + $clusterParam = @{} + CopyKeyIf $PSBoundParameters $clusterParam 'Cluster' + + if (-not $PSBoundParameters.ContainsKey('CachePercent') -and + -not $PSBoundParameters.ContainsKey('CapacityPercent')) + { + throw "One or both of CachePercent or CapacityPercent must be specified." + } + + $pool = @(Get-StorageSubsystem -CimSession $Cluster |? AutomaticClusteringEnabled | Get-StoragePool |? IsPrimordial -eq $false) + + if ($pool.Count -eq 0) + { + Write-Error "No pool found" + return + } + + if ($pool.Count -ne 1) + { + Write-Error "Unexpected number of pools found ($($pool.Count)) - please verify environment configuration" + return + } + + $cacheDevice = @($pool | Get-PhysicalDisk -CimSession $Cluster |? Usage -eq Journal) + + if ($cacheDevice.Count -eq 0 -and -not $PSBoundParameters.ContainsKey('CapacityPercent')) + { + Write-Error "There are no identifiable cache devices (Usage = Journal) - please verify environment configuration" + return + } + + if ($cacheDevice |? HealthStatus -ne Healthy) + { + Write-Warning "There are cache devices that are not healthy - please verify environment before proceeding" + } + + if ($cacheDevice.Count -ne 0) + { + $cacheCapacity = ($cacheDevice | Measure-Object -Property Size -Sum).Sum + } + else + { + $cacheCapacity = 0 + } + + $nodes = @(Get-ClusterNode @clusterParam) + + if ($nodes |? State -ne Up) + { + Write-Warning "There are nodes which are not currently up - please verify environment before proceeding" + } + + ## + # Get available space + ## + + $availableCapacity = 0 + $totalVolumeCapacity = 0 + + # + # Get total/data space available in fleet volumes + # + + $nodes = Get-ClusterNode @clusterParam + $volumes = Get-Volume -CimSession $Cluster + + foreach ($volume in $volumes) + { + foreach ($node in $nodes) + { + if ($volume.FileSystemLabel -match "^$node(?:-.+){0,1}$") + { + LogOutput -IsVb "volume $($volume.FileSystemLabel) matched to node $node" + $availableCapacity += $volume.SizeRemaining + $totalVolumeCapacity += $volume.Size + break + } + } + } + + # + # Get size of current data disks (if any) to account for reusable space. Note this is + # physical used capacity, not logical capacity presented to VM. + # + + $curDataDiskCapacity = 0 + $dataDisk = @(Get-FleetDisk @clusterParam) + + if ($dataDisk.Count) + { + # + # Note that disk paths are local to the cluster. + # En-list list of paths to pass as single list parameter. + # + + $curDataDiskCapacity = Invoke-Command -ComputerName $Cluster -ArgumentList @(,@($dataDisk.Path)) { + + param( + [string[]] + $paths + ) + + $s = 0 + foreach ($p in $paths) + { + $s += (Get-Item $p).Length + } + $s + } + } + + # + # Add in current utilization - available for use (resize) + # + + $availableCapacity += $curDataDiskCapacity + + # + # Now validate and convert VM count from VMs/node to total VMs. + # This is consistent with New-Fleet. + # + + if (-not $PSBoundParameters.ContainsKey('VMs')) + { + $vmGroups = @(Get-ClusterGroup @clusterParam |? GroupType -eq VirtualMachine |? Name -like 'vm-*') + + # + # Limit estimate to VMs currently running? Allows for at-a-distance dynamic resizing of fleet (e.g., start + # a fraction, resize per-vm workingset so that they cover) + # Limit to a percentage of configured VMs? Not dependent on VMs being started yet. + # + + $VMs = $totalVMs = $vmGroups.Count + if ($VMs % $nodes.Count) + { + Write-Error "There are an uneven number of Fleet VMs: $VMs VMs for $($nodes.Count) nodes - please verify environment configuration" + return + } + elseif ($VMs -eq 0) + { + Write-Error "No Fleet VMs found - please verify environment configuration" + return + } + + if ($Running) + { + $vmGroups = @($vmGroups |? State -eq Online) + $VMs = $vmGroups.Count + + if ($VMs -eq 0) + { + Write-Error "No running Fleet VMs found - please verify environment configuration" + return + } + + LogOutput -IsVb "Estimate based on running VM count of $VMs of the $totalVMs total" + } + elseif ($PSBoundParameters.ContainsKey('VMPercent')) + { + # Note this is in absolute numbers of VMs. Round to an even number of VMs/node, and take 0 upward to 1/node. + $VMs = [int] ($VMPercent * $VMs / 100) + $VMs -= $VMs % $nodes.Count + if ($VMs -eq 0) { $VMs = $nodes.Count } + + LogOutput -IsVb "Estimate based on $VMs VMs, $VMPercent% of the $totalVMs total VMs (evenly distributed per node)" + } + else + { + LogOutput -IsVb "Estimate based on the $totalVMs total VMs" + } + } + else + { + $VMs *= $nodes.Count + } + + ## + # Generate estimates - one or two depending on call method + ## + + $cacheEst = $null + $capEst = $null + + # + # Truncate to the nearest 512MiB. Coerce the multiplier result back to uint64. + # + + $trunc = 512MB + if ($PSBoundParameters.ContainsKey('CachePercent') -and $cacheCapacity -ne 0) + { + $c = ([uint64]($CachePercent * $cacheCapacity)) / $VMs / 100 + $c -= $c % $trunc + + if ($c * $VMs -ge $availableCapacity) + { + Write-Warning "There is is not enough capacity to reach the specified percentage of cache capacity - please verify environment before proceeding. Truncating to available capacity." + + $c = $availableCapacity / $VMs + $c -= $c % $trunc + } + + $cacheEst = $c + } + + if ($PSBoundParameters.ContainsKey('CapacityPercent')) + { + $c = ([uint64]($CapacityPercent * $availableCapacity)) / $VMs / 100 + $c -= $c % $trunc + + $capEst = $c + } + + ## + # Pick higher (or lower) - $null is excluded automatically + ## + + if ($Higher) + { + $dataDiskCapacity = ($cacheEst, $capEst | Measure-Object -Maximum).Maximum + } + else + { + $dataDiskCapacity = ($cacheEst, $capEst | Measure-Object -Minimum).Minimum + } + + if ($null -ne $cacheEst) + { + LogOutput -IsVb ("Cache percentage of {0:P1} yielded {1:0.0}GiB/VM" -f ( + ($CachePercent/100), + ($cacheEst/1GB))) + } + + if ($null -ne $capEst) + { + LogOutput -IsVb ("Capacity percentage of {0:P1} yielded {1:0.0}GiB/VM" -f ( + ($CapacityPercent/100), + ($capEst/1GB))) + } + + if ($null -ne $cacheEst -and $null -ne $capEst) + { + if ($Higher) + { + LogOutput -IsVb "Chose HIGHER estimate" + } + else + { + LogOutput -IsVb "Chose LOWER estimate" + } + } + + ## + # Informational output + ## + + LogOutput -IsVb ("Estimated data disk is {0:0.0}GiB/VM " -f ( + ($dataDiskCapacity/1GB))) + LogOutput -IsVb ("Environment: {0} nodes, $VMs total VMs" -f ( + $nodes.Count)) + LogOutput -IsVb ("Available for data disks: {0:0.00}TiB ({1:P1}) of total {2:0.00}TiB volume capacity - {3:0.00}TiB is in use (OS disks, VM metadata, other content)" -f ( + ($availableCapacity/1TB), + ($availableCapacity/$totalVolumeCapacity), + ($totalVolumeCapacity/1TB), + (($totalVolumeCapacity - $availableCapacity)/1TB))) + + if ($cacheCapacity -ne 0) + { + LogOutput -IsVb ("Cache (v. Available): {0:0.00}TiB cache capacity, available volume capacity is {1:0.00}x cache, cache is {2:P1} of available)" -f ( + ($cacheCapacity/1TB), + ($availableCapacity/$cacheCapacity), + ($cacheCapacity/$availableCapacity))) + LogOutput -IsVb ("Cache (v. Total): {0:0.00}TiB cache capacity, total volume capacity is {1:0.00}x cache, cache is {2:P1} of total)" -f ( + ($cacheCapacity/1TB), + ($totalVolumeCapacity/$cacheCapacity), + ($cacheCapacity/$totalVolumeCapacity))) + } + + # Result is uint64 + return $dataDiskCapacity +} + +# a single named Variable with a set of values +class Variable { + + [int] $_ordinal + [object[]] $_list + [string] $_label + + Variable([string] $label, [object[]] $list) + { + $this._list = $list + $this._ordinal = 0 + $this._label = $label + } + + # current value of the Variable ("red"/"blue"/"green") + [object] value() + { + if ($null -ne $this._list -and $this._list.Count -gt 0) + { + return $this._list[$this._ordinal] + } + else + { + return $null + } + } + + # label/name of the Variable ("color") + [object] label() + { + return $this._label + } + + # increment to the next member, return whether a logical carry + # has occured (overflow) + [bool] increment() + { + # empty list passes through + if ($null -eq $this._list -or $this._list.Count -eq 0) + { + return $true + } + + # non-empty list, increment + $this._ordinal += 1 + if ($this._ordinal -eq $this._list.Count) + { + $this._ordinal = 0 + return $true + } + return $false + } + + # back to initial state + [void] reset() + { + $this._ordinal = 0 + } +} + +# a set of Variables which allows enumeration of their combinations +# this behaves as a numbering system indexing the respective Variables +# order is not specified +class VariableSet { + + [hashtable] $_set = @{} + + VariableSet([Variable[]] $list) + { + $list |% { $this._set[$_.label()] = $_ } + $this._set.Values |% { $_.reset() } + } + + # increment the enumeration + # returns true if the enumeration is complete + [bool] increment() + { + $carry = $false + foreach ($v in $this._set.Values) + { + # if the Variable returns the carry flag, increment + # the next, and so forth + $carry = $v.increment() + if (-not $carry) { break } + } + + # done if the most significant carried over + return $carry + } + + # enumerator of all Variables + [object] getset() + { + return $this._set.Values + } + + # return value of specific Variable + [object] get([string]$label) + { + return $this._set[$label].value() + } + + # return a label representing the current value of the set, following the input label template + # add a leading '-' to get a seperator + # use a leading '$' to eliminate repetition of label (just produce value) + [string] label([string[]] $template) + { + return $($template |% { + + $str = $_ + $pfx = '' + $done = $false + $norep = $false + do { + switch ($str[0]) + { + '-' { + $pfx = '-' + $str = $str.TrimStart('-') + } + '$' { + $norep = $true + $str = $str.TrimStart('$') + } + default { + $done = $true + } + } + } while (-not $done) + + $lookstr = $str + if ($norep) { + $str = '' + } + + # only produce labels for non-null, non-empty (as string) values + if ($this._set.Contains($lookstr) -and $null -ne $this._set[$lookstr].value()) + { + $vstr = [string] $this._set[$lookstr].value() + if ($vstr.Length) { "$pfx$str" + $vstr } + } + }) -join $null + } +} + +function GetDoneFlags +{ + [CmdletBinding()] + param( + [Parameter( + Mandatory = $true + )] + [string] + $DonePath, + + [Parameter( + Mandatory = $true + )] + [int] + $GoEpoch, + + [Parameter( + Mandatory = $true + )] + [Microsoft.FailoverClusters.PowerShell.ClusterGroup[]] + $VM, + + [Parameter()] + [double] + $Timeout = 120, + + [Parameter()] + [switch] + $AssertNone, + + [Parameter()] + [string] + $Tag = '' + ) + + # Hash of VMs to wait on. false indicates not done, true done. Prepopulate to filter flags. + $vmDone = @{} + foreach ($v in $VM) + { + $vmDone[$v.Name] = $false + } + + $done = 0 + $tries = 0 + $t0 = Get-Date + $t1 = $t2 = $null + $itdur = $totdur = New-TimeSpan -Seconds 0 + + $timedOut = $false + + do { + + if ($null -ne $t1) { + Start-Sleep 2 + } + + # increment attempts and capture start of the iteration + $tries += 1 + $t1 = Get-Date + + $doneFile = Get-ChildItem "$DonePath-*" + + foreach ($f in $doneFile) + { + # Now split the VM name from the flag file name (done-vm-xyz); ignore malformed, + # ones which are not in our expected set, and ones already done. + $names = $f.Name -split '-',2 + if ($names.Count -eq 2 -and + $vmDone.ContainsKey($names[1]) -and + $vmDone[$names[1]] -eq $false) + { + $thisDone = [int](Get-Content $f.FullName -ErrorAction SilentlyContinue) + if ($thisDone -eq $GoEpoch) { + + # if asserting that none should be complete, this is an error! + if ($AssertNone) { + LogOutput -ForegroundColor Red "ERROR: $($names[1]) is already done" + } + + $done += 1 + $vmDone[$names[1]] = $true + } + } + + $t2 = Get-Date + $itdur = $t2 - $t1 + $totdur = $t2 - $t0 + + if ((-not $timedOut) -and $Timeout -ne 0 -and $totdur.TotalSeconds -ge $Timeout) + { + $timedOut = $true + + # Detail - time out early for the assertion test so that we don't run the risk of + # this running past the total iteration time and being spoofed into failure by + # seeing actual completions. + # + # Normally these sweeps take O(1)s even for large numbers of VMs. + # + # In the completion test we want to finish the full sweep so we can provide + # the correct counts. + if ($AssertNone) + { + break + } + } + } + + # color status message per condition + if ($AssertNone -and $timedOut) { + $color = [System.ConsoleColor]::Yellow + $Tag = $Tag + " TIMED OUT " + } elseif (($AssertNone -and $done -ne 0) -or $done -ne $VM.Count) { + $color = [System.ConsoleColor]::Yellow + } else { + $color = [System.ConsoleColor]::Green + } + + LogOutput -ForegroundColor $color "$Tag CHECK $tries : $done/$($VM.Count) done " $('({0:F2}s, total {1:F2}s)' -f $itdur.TotalSeconds, $totdur.TotalSeconds) + + # loop if not asserting, not all vms are done, and still have timeout to use + } while ((-not $AssertNone) -and $done -ne $VM.Count -and (-not $timedOut)) + + # asserting that none should be complete? + if ($AssertNone) + { + # this is the right regardless of whether the check timed out + # timeout is not ideal but is also not failure + return ($done -eq 0) + } + + # return incomplete run failure + elseif ($done -ne $VM.Count) { + LogOutput -ForegroundColor Red ABORT: only received "$done/$($VM.Count)" completions in $tries attempts + + # log out VMs not done + $en = $vmDone.GetEnumerator() + while ($en.MoveNext()) + { + if ($en.Value -eq $false) { + LogOutput -ForegroundColor Yellow NOT DONE: $en.Key + } + } + + return $false + } + + # all worked! + return $true +} + +function ClearDoneFlags +{ + [CmdletBinding()] + param( + [Parameter( + Mandatory = $true + )] + [string] + $DonePath + ) + + Get-ChildItem "$DonePath-*" | Remove-Item -Force +} + +function GetProfileDuration +{ + [CmdletBinding()] + param( + [Parameter( + ParameterSetName = "ByProfilePath", + Mandatory = $true + )] + [string] + $ProfilePath, + + [Parameter( + ParameterSetName = "ByProfileXML", + Mandatory = $true + )] + [xml] + $ProfileXml, + + [switch] + $Total, + + [switch] + $Warmup, + + [switch] + $Duration, + + [switch] + $Cooldown + ) + + if ($PSCmdlet.ParameterSetName -eq "ByProfilePath") + { + $ProfileXml = [xml] (Get-Content $ProfilePath) + } + + if ($null -eq $ProfileXml) + { + Write-Error "XML profile not readable" + return + } + + $timeSpans = $ProfileXml.SelectNodes("Profile/TimeSpans/TimeSpan") + + if ($null -eq $timeSpans) + { + Write-Error "XML Profile does not contain TimeSpan elements - verify this is a profile" + return + } + + $w = $d = $c = 0 + $timeSpans |% { + if ($_.Item("Warmup")) { $w += [int] $_.Warmup } + if ($_.Item("Duration")) { $d += [int] $_.Duration } + if ($_.Item("Cooldown")) { $c += [int] $_.Cooldown } + } + + if ($PSBoundParameters.ContainsKey('Total')) + { + return $w + $d + $c + } + + if ($PSBoundParameters.ContainsKey('Warmup')) { $w } + if ($PSBoundParameters.ContainsKey('Duration')) { $d } + if ($PSBoundParameters.ContainsKey('Cooldown')) { $c } +} + +function Clear-FleetRunState +{ + [CmdletBinding()] + param( + [Parameter()] + [string] + $Cluster = "." + ) + + ############# + $clusterParam = @{} + CopyKeyIf $PSBoundParameters $clusterParam 'Cluster' + + # Go/Done Paths + ($goPath, $donePath) = Get-FleetPath -PathType Go,Done @clusterParam + + # Scrub, reset go counter to zero + Get-ChildItem "$donePath-*" | Remove-Item -Force + Write-Output 0 > $goPath +} + +function Create-TemplateRunScript +{ + [CmdletBinding()] + param( + [string] + $AddSpec = "", + + [string] + $controlPath, + + [string] + $RunFile = "sweep.ps1", + + [string] + $RunProfileFile = "sweep.xml" + ) + + # Pause for SMB dir/info cache to expire and surface go flag. + # Runner will wait for new content - note unique guid. + Start-Sleep $smbInfoCacheLifetime + + $vmprof = "L:\control\$RunProfileFile" + $guid = [System.Guid]::NewGuid().Guid + + $f = Join-Path $controlPath $RunFile + $(Get-Command Set-RunProfileTemplateScript).ScriptBlock |% { + + # apply subsitutions to produce a new run file + $line = $_ + $line = $line -replace '__AddSpec__',$AddSpec + $line = $line -replace '__Profile__',$vmprof + $line = $line -replace '__Unique__',$guid + $line + + } | Out-File "$($f).tmp" -Encoding ascii -Width ([int32]::MaxValue) + if (Test-Path $f) { Remove-Item $f -Force } + Rename-Item "$($f).tmp" $RunFile +} + +function Start-FleetRun +{ + [CmdletBinding()] + param( + [Parameter()] + [string] + $Cluster = ".", + + [Parameter()] + [string] + $AddSpec = "", + + [Parameter( + ParameterSetName = "ByProfilePath", + Mandatory = $true + )] + [string] + $ProfilePath, + + [Parameter( + ParameterSetName = "ByProfileXml", + Mandatory = $true + )] + [xml] + $ProfileXml, + + [Parameter(ParameterSetName = "ByProfilePath")] + [Parameter(ParameterSetName = "ByProfileXml")] + [string] + $RunFile = "sweep.ps1", + + [Parameter(ParameterSetName = "ByProfilePath")] + [Parameter(ParameterSetName = "ByProfileXml")] + [string] + $RunProfileFile = "sweep.xml", + + [Parameter( + ParameterSetName = "ByPrePlaced", + Mandatory = $true + )] + [switch] + $PrePlaced, + + [Parameter( + ParameterSetName = "ByPrePlaced", + Mandatory = $true + )] + [int] + $Duration, + + [Parameter()] + [switch] + $Async, + + [Parameter()] + [string[]] + $pc = $null, + + [Parameter()] + [ValidateRange(1,60)] + [int] + $SampleInterval = 1 + ) + + # + # Core utility to put a given single run/iteration into the fleet with the + # go/done response mechanism. + # + # * ByPrePlaced - a pre-placed run script (*.ps1) is assumed, and the expected + # duration must be provded. + # * ByProfilePath - a pre-placed DISKSPD profile is specified. A template run + # script is emitted to run. + # + + ############# + $clusterParam = @{} + CopyKeyIf $PSBoundParameters $clusterParam 'Cluster' + + # Async is not compatible with performance counter capture. Consider supporting fully external management + # of async runs (including logman/done flag checks) for future. + if ($PSBoundParameters.ContainsKey('pc') -and $Async) + { + Write-Error "Performance counter captures (pc) cannot be done for Async runs" + return + } + + # Paths including node-local CSV path to results (for blg drop) + ($controlPath, $goPath, $donePath) = Get-FleetPath -PathType Control,Go,Done @clusterParam + $resultPathLocal = Get-FleetPath -PathType Result -Local @clusterParam + + # Get expected run duration in the implicit cases + switch ($psCmdlet.ParameterSetName) + { + "ByPrePlaced" { break } + "ByProfileXml" + { + $Duration = GetProfileDuration -ProfileXml $ProfileXml -Total + } + "ByProfilePath" + { + $Duration = GetProfileDuration -ProfilePath $ProfilePath -Total + } + } + + if ($null -eq $Duration -or $Duration -eq 0) + { + Write-Error "Run duration could not be determined, cannot continue" + return + } + + # Place profile in the control area? + switch ($psCmdlet.ParameterSetName) + { + "ByPrePlaced" + { + # Pause for SMB dir/info cache to expire and surface preplaced file. + # Dropping go flag will release the run. + Start-Sleep $smbInfoCacheLifetime + break + } + "ByProfileXml" + { + $runProfileFilePath = Join-Path $controlPath $RunProfileFile + Remove-Item $runProfileFilePath -Force -ErrorAction SilentlyContinue + $ProfileXml | Convert-FleetXmlToString | Out-File $runProfileFilePath -Encoding ascii -Width ([int32]::MaxValue) -Force + } + "ByProfilePath" + { + $runProfileFilePath = Join-Path $controlPath $RunProfileFile + Remove-Item $runProfileFilePath -Force -ErrorAction SilentlyContinue + Copy-Item $ProfilePath $runProfileFilePath -Force + } + } + + # Get nodes for performance counter capture (if needed) + if ($null -ne $pc) + { + $nodes = Get-ClusterNode @clusterParam |? State -eq Up | Sort-Object -Property Name + } + + # VMs expected to participate + $vms = @(Get-ClusterGroup @clusterParam |? GroupType -eq VirtualMachine |? Name -like "vm-*" |? State -eq Online) + + if (Get-FleetPause @clusterParam) + { + $clearPause = $true + } + else + { + $clearPause = $false + } + + # Kick off go iteration + $goepoch = [int](Get-Content $goPath -ErrorAction SilentlyContinue) + $goepoch += 1 + + LogOutput "START Go Epoch: $goepoch" + Write-Output $goepoch > $goPath + + # Create templated run script to inject this profile? + if ($psCmdlet.ParameterSetName -ne "ByPrePlaced") + { + Create-TemplateRunScript $AddSpec $controlPath $RunFile $RunProfileFile + } + + # capture time zero + $t0 = get-date + + # release pause + if ($clearPause) + { + LogOutput "CLEAR pause at Go" + Clear-FleetPause @clusterParam + } + + # arm flag to guarantee teardown of logman instances on exception + $stopLogman = $false + + try + { + # start performance counter capture + if ($null -ne $pc) + { + StartLogman -Computer $nodes -Name $AddSpec -Counters $pc -SampleInterval $SampleInterval + $stopLogman = $true + } + + ###### + + # If total run is more than ffMin seconds, sleep to ffCheckAt and look for false completions + # for up to ffCheckWindow seconds. Controlling the check duration is important in case it + # takes an unexpectedly long time to go through the done flags, regardless of reason. + + $fastFailMin = 60 + $fastFailCheckAt = 20 + $fastFailCheckWindow = 15 + + if ($Duration -gt $fastFailMin) + { + # Account for time taken to this point (logman/clear) to arrive at the check time + $td = (Get-Date) - $t0 + $remainingSleep = $fastFailCheckAt - $td.TotalSeconds + + if ($remainingSleep -gt 0) + { + LogOutput SLEEP TO RUN CHECK "($('{0:0.00}' -f $remainingSleep) seconds)" + Start-Sleep -Milliseconds ($remainingSleep*1000) + $td = (Get-Date) - $t0 + } + + LogOutput RUN CHECK Go Epoch: $goepoch + + # Calculate effective time to check flags: how far into the timeout interval + # are we already. Note that this may wind up missing early completion if the run + # is very short. TBD positive successful completion ack as opposed to job completion. + $fastFailCheckTimeout = $fastFailCheckWindow - ($td.TotalSeconds - $fastFailCheckAt) + + if ($fastFailCheckTimeout -gt 0) + { + if (-not (GetDoneFlags -DonePath $donePath -GoEpoch $goepoch -VM $vms -AssertNone -Tag 'RUN' -Timeout $fastFailCheckTimeout)) + { + throw "Unexpected early completion of load, please check profile and virtual machines for errors" + } + } + LogOutput -ForegroundColor green RUN CHECK PASS Go Epoch: $goepoch + } + + # If this is an async run, done here. + # Consider exposing logman and done flag check for fully external async management of runs. + if ($Async) + { + return + } + + # Sleep for the remaining interval + $td = (Get-Date) - $t0 + $remainingsleep = $Duration - $td.TotalSeconds + + if ($remainingsleep -gt 0) + { + LogOutput SLEEP TO END "($('{0:0.00}' -f $remainingsleep) seconds)" + Start-Sleep -Milliseconds ($remainingsleep*1000) + } + + # Stop performance counter capture + # Do this before polling done flags, which can take time + if ($stopLogman) + { + StopLogman -Computer $nodes -Name $AddSpec -Path $resultPathLocal + $stopLogman = $false + } + + if (-not (GetDoneFlags -DonePath $donePath -GoEpoch $goepoch -VM $vms -Tag COMPLETION)) + { + throw "Load did not fully complete, please check profile and virtual machines for errors" + } + } + catch + { + # Pause fleet under error conditions + + Set-FleetPause + throw + } + finally + { + # Stop and abandon capture if normal termination did not occur (do not pull blg back) + if ($stopLogman) + { + StopLogman -Computer $nodes -Name $AddSpec + } + + if (-not $Async) + { + # Work around two issues for callers: + # + # * conflicting handles on the XML result files (sleep) + # * delegated xcopy in StopLogman appears to leave references that + # hold up access to BLG until the CSV is bounced. + # + # TBD track these down. Overlap the 20s wait for XML handles with the CSV move. + # + # Async runs will need to manage this (if needed) directly if/when full external + # management is supported. + + $t0 = Get-Date + $null = Get-ClusterSharedVolume @clusterParam |? Name -match $collectVolumeName | Move-ClusterSharedVolume + + $td = (Get-Date) - $t0 + $tms = 20*1000 - $td.TotalMilliseconds + if ($tms -gt 0) + { + Start-Sleep -Milliseconds $tms + } + } + } +} + +function Start-FleetSweep +{ + [CmdletBinding()] + param( + [Parameter()] + [string] + $Cluster = ".", + + [Parameter()] + [string] + $AddSpec, + + [Parameter()] + [string] + $RunTemplate, + + [Parameter()] + [string] + $RunFile = "sweep.ps1", + + [Parameter()] + [string[]] + $LabelTemplate = @('b','t','o','w','p','iops','-$addspec'), + + [Parameter(Mandatory =$true)] + [int[]] + $b, + + [Parameter(Mandatory =$true)] + [int[]] + $t, + + [Parameter(Mandatory =$true)] + [int[]] + $o, + + [Parameter(Mandatory =$true)] + [int[]] + $w, + + [Parameter()] + [ValidateSet('r','s','si')] + [string[]] + $p = 'r', + + [Parameter()] + [int[]] + $iops = $null, + + [Parameter()] + [ValidateRange(1,[int]::MaxValue)] + [int] + $d = 30, + + [Parameter()] + [ValidateRange(0,[int]::MaxValue)] + [int] + $Warm = 60, + + [Parameter()] + [ValidateRange(0,[int]::MaxValue)] + [int] + $Cool = 30, + + [Parameter()] + [string[]] + $pc = $null, + + [Parameter()] + [ValidateRange(1,60)] + [int] + $SampleInterval = 1 + ) + + ############# + $clusterParam = @{} + CopyKeyIf $PSBoundParameters $clusterParam 'Cluster' + + # Paths including node-local CSV path to results (for blg drop) + ($controlPath, $goPath, $donePath) = Get-FleetPath -PathType Control,Go,Done @clusterParam + + # Convert RunFile to fully qualified. Any type of non-plain filename is not allowed: this will + # always be placed in the control path. + if ($RunFile.Contains('\')) + { + Write-Error "$RunFile must be a plain filename, to be created in the control path ($controlPath)" + return + } + $RunFilePath = Join-Path $controlPath $RunFile + + function NewRunFile + { + param( + [VariableSet] + $vs, + + [string] + $runtemplate + ) + + $guid = [System.Guid]::NewGuid().Guid + + # Use template script if offered, else builtin + $(if ($runtemplate.Length -gt 0) + { + Get-Content $runtemplate + } + else + { + (Get-Command Set-SweepTemplateScript).ScriptBlock + }) |% { + + # apply current subsitutions to produce a new run file + + $line = $_ + + foreach ($v in $vs.getset()) { + # non-null goes in as is, null goes in as evaluatable $null + if ($null -ne $v.value()) { + $vsub = $v.value() + } else { + $vsub = '$null' + } + $line = $line -replace "__$($v.label())__",$vsub + } + + $line = $line -replace '__Unique__',$guid + $line + + } | Out-File "$($RunFilePath).tmp" -Encoding ascii -Width ([int32]::MaxValue) + if (Test-Path $RunFilePath) { Remove-Item $RunFilePath -Force } + Rename-Item "$($RunFilePath).tmp" $RunFile + } + + function ShowRun( + [VariableSet] $vs + ) + { + # show current substitions (minus the underscore bracketing) + LogOutput -ForegroundColor green RUN PARAMETERS + foreach ($v in $vs.getset()) { + if ($null -ne $v.value()) { + $vsub = $v.value() + } else { + $vsub = '$null' + } + LogOutput -ForegroundColor green "`t$($v.label()) = $($vsub)" + } + } + + function GetRunDuration( + [VariableSet] $vs + ) + { + $vs.get('d') + $vs.get('Warm') + $vs.get('Cool') + } + + ############# + + # Start paused so that when run state is cleared VMs do not enter free run + # and begin executing the first run file for this sweep pass. + Set-FleetPause @clusterParam + Clear-FleetRunState @clusterParam + + # construct the Variable list describing the sweep + + ############################ + ############################ + ## Modify from here down + ############################ + ############################ + + # add any additional sweep parameters here to match those specified on the command line + # ensure your sweep template script contains substitutable elements for each + # + # __somename__ + # + # bracketed by two underscore characters. Consider adding your new parameters to + # the label template so that result files are well-named and distinguishable. + + $v = @() + $v += [Variable]::new('b',$b) + $v += [Variable]::new('t',$t) + $v += [Variable]::new('o',$o) + $v += [Variable]::new('w',$w) + $v += [Variable]::new('p',$p) + $v += [Variable]::new('iops',$iops) + $v += [Variable]::new('d',$d) + $v += [Variable]::new('Warm',$warm) + $v += [Variable]::new('Cool',$cool) + $v += [Variable]::new('AddSpec',$addspec) + + $sweep = [VariableSet]::new($v) + + try + { + do + { + Write-Host -ForegroundColor Cyan ('-'*20) + + ShowRun $sweep + NewRunFile $sweep $RunTemplate + + Start-FleetRun -PrePlaced @clusterParam -AddSpec ($sweep.label($LabelTemplate)) -Duration (GetRunDuration $sweep) -pc $pc -SampleInterval $SampleInterval -ErrorVariable e + + if ($e.Count) + { + return + } + + } while (-not $sweep.increment()) + } + finally + { + # Set pause to quiesce the system. + # Remove the sweep runfile. + if (-not (Get-FleetPause)) + { + Set-FleetPause @clusterParam + } + + if (Test-Path $RunFilePath) + { + Remove-Item $RunFilePath -Force + } + } +} + +function Start-FleetWriteWarmup +{ + [CmdletBinding()] + param( + [Parameter()] + [string] + $Cluster = '.', + + [Parameter(Mandatory = $true)] + [uint64] + $VMWorkingSetSize + ) + + $nodes = Get-ClusterNode -Cluster $Cluster |? State -eq Up + + $vm = @(Get-FleetVM -Cluster $Cluster |? State -eq Ok) + if ($vm.Count -eq 0) + { + throw 'No running VMs found' + } + + # Write warmup is a sequential workingset rewrite. One example of similar issues addressed in public + # specification is in the SNIA Solid State Performance Test Specification's "Workload Independent + # Pre-Conditioning" (see v 2.0.2 sec 3.3 and references throughout). Although VM Fleet is operating + # at an aggregate level, the same princple applies the the virtual storage stack. + # + # In general VM Fleet will avoid most known reasons to need to do this, at least for the sake of + # its current goal of measuring conditions in long-term running state: it does not use block cloning + # or leave workingsets in sparse states (currently), for instance. + # + # However, a major reason for VM Fleet to handle this is on parity volumes: Spaces tracks whether parity + # stripes have been written so that it can calculate parity incrementally v. requiring a full stripe + # read/write. The tracking for this also allows the read path to optimize away the reads, similar to + # sparse/valid data length management. If portions of the workingset wind up being initially allocated + # v. parity storage, reads to that content would be optimized away until a write workload came in and + # hit it. While certainly an interesting condition to evaluate, it is indeed a distinct and shorter term + # operating condition. + + LogOutput "WRITE WARMUP: START" + + # + # Now warm up. Build a write profile with one thread of sequential IO. + # + + $t0 = $null + $warmupRun = 3600 * 24 * 7 + $blockSize = 128KB + + $dynamicProfile = Get-FleetProfileXml -Name Peak -ThreadsPerTarget 1 -WriteRatio 100 -BlockSize $blockSize -Alignment $blockSize -RequestCount 8 -Sequential -Duration $warmupRun + + $t0 = Get-Date + Start-FleetRun -ProfileXml $dynamicProfile -Async + + $pc = @('\Cluster CSVFS(_Total)\Write Bytes/sec', + '\Cluster Storage Hybrid Disks(_Total)\Disk Write Bytes/sec' + ) + + $sampleInterval = 15 + + # Track total CSV write bytes to enforce minimum traversal of the logical workingset. + # The physical workingset traversal (and disk write bytes) are informative, only. + $totalws = $vm.Count * $VMWorkingSetSize + $totalwb = [uint64] 0 + + # Traverse 2.2x since we don't have direct visibility into each VM's progress + $traversews = 2.2 * $totalws + + LogOutput ("WRITE WARMUP: $($vm.Count) VMs @ {0:0.00}GiB/VM => {1:0.00}TiB total workingset to traverse" -f ( + ($VMWorkingSetSize/1GB), + ($traversews/1TB))) + + try + { + do + { + # Aggregate samples across the cluster - note that all counters are currently unique + # by counter name, just use that. + $samples = Get-Counter $pc -ComputerName $nodes -SampleInterval $sampleInterval + $samplesAgg = @{} + $samples.CounterSamples |% { + + # Counter name is fifth element when split by seperator: \\machine\set(instance)\ctr + $ctr = ($_.Path -split '\\')[4] + if ($samplesAgg.ContainsKey($ctr)) + { + $samplesAgg.$ctr += $_.CookedValue + } + else + { + $samplesAgg.$ctr = $_.CookedValue + } + } + + $csvwb = [uint64] ($samplesAgg."Write Bytes/sec") + $dwb = [uint64] ($samplesAgg."Disk Write Bytes/sec") + + $totalwb += $csvwb * $sampleInterval + + LogOutput ("Write rate: {0:0.00}GiB/s, total {1:0.00}TiB {2:P1} complete (physical: {3:0.00}GiB/s, {4:0.0}x amplification)" -f ( + ($csvwb/1GB), + ($totalwb/1TB), + ($totalwb/$traversews), + ($dwb/1GB), + ($dwb/$csvwb))) + + } while ($traversews -gt $totalwb) + } + finally + { + # Halt the warmup (throw/normal completion) + Set-FleetPause + } + + $td = (Get-Date) - $t0 + LogOutput "WRITE WARMUP COMPLETE: took $(TimespanToString $td)" +} + +function Start-FleetReadCacheWarmup +{ + [CmdletBinding()] + param( + [Parameter()] + [string] + $Cluster = '.', + + [Parameter(Mandatory = $true)] + [xml] + $ProfileXml, + + [Parameter(Mandatory = $true)] + [uint64] + $VMWorkingSetSize + ) + + $sess = New-CimSession -ComputerName $Cluster + $s2d = Get-ClusterS2D -CimSession $sess + $nodes = Get-ClusterNode -Cluster $Cluster |? State -eq Up + + # Cache disabled is normal for flat configurations, no read warmup. + if ($s2d.CacheState -ne 'Enabled') + { + LogOutput "READ CACHE WARMUP: cache not enabled, no warmup to perform" + return + } + + # Get read workingset of the profile - if there is none, there is no warmup. + # More than one named target is unsupported for now. + $readWorkingSet = GetFleetProfileFootprint -ProfileXml $ProfileXml -Read + if ($readWorkingSet.Count -eq 0) + { + LogOutput "READ CACHE WARMUP: profile does not define a read workingset, no warmup to perform" + return + } + if ($readWorkingSet.Count -gt 1) + { + throw "Read Cache Warmup does not currently support more than one target/profile ($($readWorkingset.Count) found)" + } + + $pool = @(Get-StorageSubsystem -CimSession $sess |? AutomaticClusteringEnabled | Get-StoragePool |? IsPrimordial -eq $false) + $poold = @($pool | Get-PhysicalDisk -CimSession $sess) + + # Compare capacity devices against cache mode - any operating with read cache require warmup + $capg = @($poold |? Usage -ne Journal | Group-Object -NoElement -Property MediaType) + $cached = @($poold |? Usage -eq Journal) + + # Although an administrator is encouraged to bring up a flat configuration with CacheState = Disabled, + # it is not required. Assume this is as-intended. + if ($cached.Count -eq 0) + { + LogOutput "READ CACHE WARMUP: no cache devices in cache-ready configuration, no warmup to perform" + return + } + + if ($capg.Count) + { + $reasons = 0 + foreach ($type in $capg.Name) + { + switch ($type) + { + 'HDD' { if ($s2d.CacheModeHDD -ne 'WriteOnly') { $reasons += 1 }} + 'SSD' { if ($s2d.CacheModeSSD -ne 'WriteOnly') { $reasons += 1 }} + } + } + + if ($reasons -eq 0) + { + LogOutput "READ CACHE WARMUP: capacity devices are not caching reads, no warmup to perform" + return + } + } + else + { + return + } + + $vm = @(Get-FleetVM -Cluster $Cluster |? State -eq Ok) + if ($vm.Count -eq 0) + { + throw 'No running VMs found' + } + + # Now reason about total cache capacity and the profile's read workingset footprint across the running VMs. + # + # Produce a warmup profile covering up to 60% of the cache on that footprint. In cache rich environments + # this may span the entire fleet disk capacity. Example: + # + # * 10 TiB cache capacity -> 6 TiB potential warmup + # * 48 VMs -> ~128 GiB potential warmup/VM + # * data disk of 512 GiB + # * profile's read IO base of 5 GiB -> 100% span -> 507GiB read workingset + # * => generate warmup profile with IO base of 5 GiB -> max of 133 GiB (128 GiB total warmup) + + $warmPct = 0.6 + $cacheCapacity = ($cached | Measure-Object -Property AllocatedSize -Sum).Sum + $cacheCapacityVM = $warmPct * $cacheCapacity / $vm.Count + + LogOutput "READ CACHE WARMUP: START" + LogOutput -IsVb ("$($cached.Count) cache devices totaling {0:0.00} TiB capacity" -f ($cacheCapacity/1TB)) + LogOutput -IsVb ("$($vm.Count) VMs => {0:0.00} GiB cache available per VM (at {1:P1} target of total)" -f + (($cacheCapacityVM/1GB), + $warmPct)) + + $readBase = $readWorkingSet.Values.BaseOffset + $readMax = $readWorkingSet.Values.MaxOffset + if ($readMax -eq 0) + { + $readMax = $VMWorkingSetSize + } + + LogOutput -IsVb ("Profile read range: [{0:0.00}GiB - {1:0.00}GiB] ({2:0.00}GiB total) of total {3:0.00}GiB VM workingset" -f + (($readBase/1GB), + ($readMax/1GB), + (($readMax - $readBase)/1GB), + ($VMWorkingSetSize/1GB))) + + if (($readMax - $readBase) -gt $cacheCapacityVM) + { + $readMax = $readBase + $cacheCapacityVM + LogOutput -IsVb ("Limit to warmable: [{0:0.00}GiB - {1:0.00}GiB] ({2:0.00}GiB total)" -f + (($readBase/1GB), + ($readMax/1GB), + (($readMax - $readBase)/1GB))) + } + else + { + LogOutput -IsVb "Full range warmable" + } + + # + # Now warm up. Build read profile with two threads, each thread warming half of the target: one on even numbered + # blocks and one on odd via one thread starting at ThreadStride 1/2 way around the target + blocksize. + # This sidesteps sequential IO detection. Blocksize must be small enough to meet cache criteria even when L1 + # age is under threshold goal (just > ~1 day). + # + # Time is a placeholder; in practice, the run will be failed if we loop the workingset more times than expected + # without converging. An explicitly indefinite runtime would be preferable. + # + + $t0 = $null + $warmupRun = 3600 * 24 * 7 + $blockSize = 56KB + + # Now align the base/max to our warmup block size. The difference between this and the actual range is not material + # at the scale of GiB of workingset; if this assumption does not hold it could be reasonable to autoscale the blocksize + # to better fit the (assumedly very small) workingset size. Base up, max down. + + $readBaseAligned = $readBase + $blockSize - 1 + $readBaseAligned -= $readBaseAligned % $blockSize + $readMaxAligned = $readMax - ($readMax % $blockSize) + + $threadStride = $blockSize + $blockSize*([uint64](($readMaxAligned - $readBaseAligned)/$blockSize))/2 + + # Too small? + if ($readMaxAligned -le $readBaseAligned -or + $threadStride -le $readBaseAligned -or + $threadStride -ge $readMaxAligned) + { + LogOutput "READ CACHE WARMUP COMPLETE: minimal workingset" + return + } + + $dynamicProfile = Get-FleetProfileXml -Name Peak -BaseOffset $readBaseAligned -MaxOffset $readMaxAligned -ThreadsPerTarget 2 -WriteRatio 0 -BlockSize $blockSize -Alignment ($blockSize*2) -ThreadStride $threadStride -RequestCount 8 -Sequential -Duration $warmupRun + + try + { + # Trigger temporary change to cache on first read miss, avoiding time which might be + # required to pass the cache aging and multiple-hit requirements, among others. + # Sink warnings (2019 does not support this). + SetCacheBehavior -Cluster $Cluster -PopulateOnFirstMiss 3>$null + + $t0 = Get-Date + Start-FleetRun -ProfileXml $dynamicProfile -Async + + $pc = @('\Cluster Storage Hybrid Disks(_Total)\Cache Hit Reads/sec', + '\Cluster Storage Hybrid Disks(_Total)\Cache Miss Reads/sec', + '\Cluster Storage Hybrid Disks(_Total)\Cache Populate Bytes/sec', + '\Cluster Storage Hybrid Disks(_Total)\Disk Reads/sec', + '\Cluster Storage Hybrid Disks(_Total)\Disk Read Bytes/sec' + ) + + $sampleInterval = 15 + + # Track total disk read bytes to enforce minimum traversal of the working set + # after reaching potential completion criteria. + $totalws = $vm.Count * ($readMax - $readBase) + $pendingCompletion = $false + $completeMsg = $null + + $totaldrb = [uint64] 0 + $completedrb = [uint64] 0 + $anyWarmup = $false + + # AtComp/MinMax workingset traversals + # + # * make AtComp traverses once completion condition triggers + # * make at least Min traverses if any warmup begins (any step sees non-completion) + # * make at most Max traverses, then flag inability to complete + # + # Note AtComp takes precedence over Min if completion triggers on the first step. This + # allows fast(est) verification of an already warmed system. + + $traverseAtComp = 1.2 * $totalws + $traverseMin = 2.0 * $totalws + $traverseMax = 20.0 * $totalws + + LogOutput ("READ CACHE WARMUP: $($vm.Count) VMs @ {0:0.00}GiB/VM => {1:0.00}TiB total workingset" -f ( + (($readMax - $readBase)/1GB), + ($totalws/1TB))) + + do + { + # Aggregate samples across the cluster - note that all counters are currently unique + # by counter name, just use that. + $samples = Get-Counter $pc -ComputerName $nodes -SampleInterval $sampleInterval + $samplesAgg = @{} + $samples.CounterSamples |% { + + # Counter name is fifth element when split by seperator: \\machine\set(instance)\ctr + $ctr = ($_.Path -split '\\')[4] + if ($samplesAgg.ContainsKey($ctr)) + { + $samplesAgg.$ctr += $_.CookedValue + } + else + { + $samplesAgg.$ctr = $_.CookedValue + } + } + + $cpb = $samplesAgg."Cache Populate Bytes/sec" + $chr = $samplesAgg."Cache Hit Reads/sec" + $cmr = $samplesAgg."Cache Miss Reads/sec" + $dr = $samplesAgg."Disk Reads/sec" + $drbt = [uint64] ($samplesAgg."Disk Read Bytes/sec" * $sampleInterval) + + if ($pendingCompletion) + { + $completedrb += $drbt + } + $totaldrb += $drbt + $wsMsg = $null + + # Miss rate hitting near actual zero? + if ([int][math]::Floor($cmr) -eq 0) + { + if (-not $pendingCompletion) + { + LogOutput "PENDING COMPLETION: reached zero cache miss rate" + $pendingCompletion = $true + } + elseif ($completedrb -gt $traverseAtComp -and + ($anyWarmup -eq $false -or $totaldrb -gt $traverseMin)) + { + $completeMsg = ("reached zero cache miss rate at {0:0.00}TiB ws traversal" -f ( + ($totaldrb/1TB))) + break + } + } + # Sustaining hit:miss ratio > 100 for an entire traversal + elseif ($chr/$cmr -gt 100) + { + if (-not $pendingCompletion) + { + LogOutput "PENDING COMPLETION: hit:miss ratio > 100" + $pendingCompletion = $true + } + elseif ($completedrb -gt $traverseAtComp -and + ($anyWarmup -eq $false -or $totaldrb -gt $traverseMin)) + { + $completeMsg = ("hit:miss ratio > 100 at {0:0.00}TiB ws traversal" -f ( + ($totaldrb/1TB))) + break + } + } + elseif ($pendingCompletion) + { + # Not meeting criteria. Reset fuse. + $completedrb = [uint64] 0 + $pendingCompletion = $false + $wsMsg = ', resetting completion checks due to high miss rate' + + $anyWarmup = $true + } + + # Inform workingset state + if ($null -eq $wsMsg) + { + if ($pendingCompletion) + { + $m = $null + $toGo = 0 + $compToGo = 0 + $minToGo = 0 + if ($traverseAtComp -ge $completedrb) { $compToGo = $traverseAtComp - $completedrb } + if ($traverseMin -ge $totaldrb) { $minToGo = $traverseMin - $totaldrb } + + # Per completion condition, min must be satisfied if any warmup condition occured. + # Note whether this is in play or not for diagnostics (fast completion). + if ($anyWarmup) + { + if ($minToGo -gt $compToGo) { $toGo = $minToGo } else { $toGo = $compToGo } + } + else + { + $toGo = $compToGo + $m = ' (minimal warmup)' + } + $wsMsg = (", {0:0.00}TiB to complete ws traversal$m" -f (($toGo)/1TB)) + } + else + { + $wsMsg = (', {0:0.00}TiB ws traversed' -f ($totaldrb/1TB)) + } + } + + LogOutput ("Read IOPS: hit {0:N0} miss {1:N0} => {2:N0} total (populate @ {3:0.0} MiB/s$wsMsg)" -f + ($chr, $cmr, $dr, ($cpb/1MB))) + + # If warmup has traversed the maximum and completion is not triggered, give up - something must have + # gone wrong with the cachable workingset estimation. + if ($totaldrb -gt $traverseMax -and -not $pendingCompletion) + { + throw "FAIL: read cache warmup has not converged" + } + + } while ($true) + } + finally + { + # Halt the warmup (throw/normal completion) + Set-FleetPause + + # Clear cache behavior change + # Sink warnings (2019 does not support this). + SetCacheBehavior -Cluster $Cluster -PopulateOnFirstMiss:$false 3>$null + } + + $td = (Get-Date) - $t0 + LogOutput "READ CACHE WARMUP COMPLETE: $completeMsg, took $(TimespanToString $td)" +} + +function Get-PerformanceCounter +{ + [CmdletBinding()] + param( + [Parameter( + Mandatory = $true + )] + [System.IO.FileInfo] + $Blg, + + [Parameter( + Mandatory = $true + )] + [int] + $Warmup, + + [Parameter( + Mandatory = $true + )] + [int] + $Duration, + + [Parameter()] + [switch] + $SumProduct, + + [Parameter( + Mandatory = $true + )] + [string[]] + $Ctrs + ) + + # import, extending the counter names with the required computername wildcard + # swallow all errors - we'll handle per-sample/countersample issues individually as long as we get data + $sample = @(Import-Counter -Path $Blg -Counter ($Ctrs |% { "\\*\$_" }) -ErrorAction SilentlyContinue -ErrorVariable e) + + # if we got nothing, something fatal has occured; throw the first error received + if (-not $sample.Count) + { + throw $e[0] + } + + # !sumproduct: return average of individual counters, multiple values + # sumproduct : return average of sumproduct of counters, single value + # + # The result of each branch is that we have an list of lists (one or more) + # which are then individually averaged into the output. In matrix terms, + # this is transposing the list from time-major order (the samples) to + # counter-major order. + + $nctr = $nresult = $sample[0].CounterSamples.Count + if ($SumProduct) + { + $nresult = 1 + } + + # vector of counter vectors - all empty to start + $result = [object[]]::new($nresult) + $result = ,@() * $nresult + + # vector of running counts of good samples to track baseline status (i.e., is it valid) + # all imported relative counters are invalid until the *second* status = 0 sample present in the + # stream. as a result all start off invalid. on sample failure, they go invalid again. + # + # all counters must be good to produce a sumproduct sample + + $ngood = @(0) * $nctr + + # check individual counter status + + $t0 = $null + foreach ($s in $sample) + { + # number of counters that don't have usable values this sample + # " with bad data + # these are different states - the first sample with valid data + # for a relative counter is not usable since its delta cannot + # be computed yet; this is why we have to wait for the second + $skip = 0 + $nbad = 0 + + # Pass 1 - check individual counter status + for ($i = 0; $i -lt $s.CounterSamples.Count; $i++) + { + # save status based on this sample ... + if ($s.CounterSamples[$i].Status -ne 0) + { + # ... bad, skip + # in practice we expect all to be PDH_INVALID_DATA = c0000bc6 + $ngood[$i] = 0 + $skip++ + $nbad++ + } + elseif (-not $ngood[$i]) + { + # ... now good, next will be ok + $ngood[$i] = 1 + $skip++ + } + else + { + # ... baselined, good + $ngood[$i]++ + } + } + + # entire row bad? + # detail: import-counter returns current time if all counters are bad (!) + # so we have to completely drop the sample + if ($nbad -eq $s.CounterSamples.Count) + { + continue + } + + # save time zero from the first valid sample + if ($null -eq $t0) + { + $t0 = $s.Timestamp + } + + # figure out how far into the sample this is - done? skip warmup? + # note that sample/counter validity is why its easier to roll through + # all rows to know when the first sample of interest is in hand. timestamp + # jitter and drops make things more complicated than simply indexing. + $td = ($s.Timestamp - $t0).TotalSeconds + if ($td -ge $Warmup + $Duration) + { + break + } + + if ($td -lt $Warmup) + { + continue + } + + + # Pass 2 - extend result + + if ($SumProduct) + { + $x = 1 + + for ($i = 0; $i -lt $nctr; $i++) + { + # any unusable value skips the sumproduct + if ($ngood[$i] -le 1) + { + $x = $null + break + } + + $x *= $s.CounterSamples[$i].CookedValue + } + + if ($null -ne $x) + { + $result[0] += ,$x + } + } + else + { + for ($i = 0; $i -lt $nctr; $i++) + { + # any unusable value skips just this counter + if ($ngood[$i] -le 1) + { + continue + } + + $result[$i] += ,$s.CounterSamples[$i].CookedValue + } + } + } + + # Average each counter strip (single sumproduct or individual) to output + foreach ($r in $result) + { + ($r | Measure-Object -Average).Average + } +} + +function ItemToDecimal +{ + [CmdletBinding()] + param( + [Parameter()] + [object] + $item + ) + + if ($null -ne $item) + { + return [decimal] $item."#text" + } + else + { + return [decimal] 0 + } +} + +# Post-process DISKSPD results per VM +function ReduceDISKSPDResultToRowResult +{ + [CmdletBinding()] + param( + [Parameter( + ValueFromPipeline = $true, + Mandatory = $true + )] + [System.IO.FileInfo] + $ResultFile + ) + + # Row object is a collection of NoteProperty constructed by parsing various properties + # from the result XML onto a new, flat object. + # + # Latency: Read/Write @ Average, Median/90/99th ptile + # IOPS + # Result filename + + begin { + $rw = @("Read", "Write") + $ms = "Milliseconds" + $av = "Average" + + $badFile = @() + } + + process { + + try { + $x = [xml](Get-Content $ResultFile) + } + catch + { + $badFile += $ResultFile + } + + # Use ordered table to produce a predictable column layout if exported. + # Result File + Average IOPS + + $props = [ordered] @{ Result = $ResultFile.BaseName } + + # Lack of target result elements indicates execution failure; TBD reporting. + $timeSpan = $x.SelectNodes('Results/TimeSpan') + $target = $x.SelectNodes('Results/TimeSpan/Thread/Target') + if ($target.Count) + { + $rio =($target | Measure-Object -Sum ReadCount).Sum + $wio =($target | Measure-Object -Sum WriteCount).Sum + $rb =($target | Measure-Object -Sum ReadBytes).Sum + $wb =($target | Measure-Object -Sum WriteBytes).Sum + + $totalTime = ($timeSpan | Measure-Object -Sum TestTimeSeconds).Sum + + $props["IOPS"] = [decimal] (($rio + $wio) / $totalTime) + $props["AverageReadIOPS"] = [decimal] ($rio / $totalTime) + $props["AverageWriteIOPS"] = [decimal] ($wio / $totalTime) + $props["AverageReadBW"] = [decimal] ($rb / $totalTime) + $props["AverageWriteBW"] = [decimal] ($wb / $totalTime) + } + else + { + $props["IOPS"] = [decimal] 0 + $props["AverageReadIOPS"] = [decimal] 0 + $props["AverageWriteIOPS"] = [decimal] 0 + $props["AverageReadBW"] = [decimal] 0 + $props["AverageWriteBW"] = [decimal] 0 + } + + # Latencies, Average and PTile + foreach ($type in $rw) + { + foreach ($ptile in @(50, 90, 99)) + { + $tile = $x.Results.TimeSpan.Latency.Bucket[$ptile] + + # ReadMilliseconds ... per ptile bucket + $prop = $type + $ms + $props[$prop + $ptile] = ItemToDecimal $tile.Item($prop) + } + + # AverageReadMillisconds + # Encode as Average|type, omit $ms + $prop = $av + $type + $ms + $props[$prop] = ItemToDecimal $x.Results.TimeSpan.Latency.Item($prop) + } + + [PsCustomObject] $props + } + + end { + + if ($badFile.Count) + { + throw "Invalid XML results, cannot post process: $($badFile.Name -join ', ')" + } + } +} +function ReduceRowResultToSummary +{ + [CmdletBinding()] + param( + [Parameter( + ValueFromPipeline = $true, + Mandatory = $true + )] + [System.Object] + $RowObject + ) + + # Row object is a collection of NoteProperty. + # Single summary property bag is composed of each decimal property reduced to aggregate by type. + # + # * *IOPS are summed + # * Average[Type]Milliseconds are SumProduct averaged with Average[Type]IOPS + # * others are averaged + # + # This does presume column name schema ReduceDISKSPDResultToRowResult, which could be better + # expressed dynamically. + + begin { + $rows = @() + $cols = @() + + $colAverage = @() + $colSumProduct = @() + $colSum = @() + } + + process { + # Get column schema (all decimal properties) + if ($cols.Count -eq 0) + { + $cols = @(@($RowObject | Get-Member |? MemberType -eq NoteProperty |? Definition -like decimal*).Name) + + $colAverage = @($cols -notlike "*IOPS" -notlike "*BW" -notlike "Average*Milliseconds") + $colSumProduct = @($cols -like "Average*Milliseconds") + $colSum = @($cols -like "*IOPS") + $colSum += @($cols -like "*BW") + } + + $rows += $RowObject + } + + end { + # Build property bag, simple averages/sums first. + $props = @{} + $rows | Measure-Object -Property $colAverage -Average |% { $props[$_.Property] = $_.Average } + $rows | Measure-Object -Property $colSum -Sum |% { $props[$_.Property] = $_.Sum } + + # The product column is always a sum aggregate; note aggregate sum is already available in the + # property bag. Compute running sumproduct and divide the aggregate to produce the average. + foreach ($col in $colSumProduct) + { + # Average[Type]Milliseconds * Average[Type]IOPS + $pcol = $col -replace "Milliseconds","IOPS" + $sumproduct = ($rows |% { $_.$col * $_.$pcol } | Measure-Object -Sum).Sum + + # Handle hard zeros (e.g., no reads, no writes) + if ($props[$pcol] -ne 0) + { + $props[$col] = $sumproduct / $props[$pcol] + } + else + { + $props[$col] = 0 + } + } + + $props + } +} + +function ProcessDISKSPDResult +{ + [CmdletBinding()] + param( + [Parameter( + Mandatory = $true + )] + [System.IO.DirectoryInfo] + $ResultDirectory, + + [Parameter()] + [string] + $AddSpec = "", + + [Parameter()] + [string] + $PerVMResultFile = "", + + [Parameter( + Mandatory = $true + )] + [int] + $Warmup, + + [Parameter( + Mandatory = $true + )] + [int] + $Duration + ) + + # common parameter set for counter interval + $intervalParam = @{} + CopyKeyIf $PSBoundParameters $intervalParam 'Warmup' + CopyKeyIf $PSBoundParameters $intervalParam 'Duration' + + # Patterns for perfctr and VM result files + $perfctrPat = (Join-Path $ResultDirectory "perfctr") + if ($PSBoundParameters.ContainsKey('AddSpec')) { $perfctrPat += "-$AddSpec"} + $perfctrPat += "-*.blg" + + $resultPat = (Join-Path $ResultDirectory "result") + if ($PSBoundParameters.ContainsKey('AddSpec')) { $resultPat += "-$AddSpec"} + $resultPat += "-*.xml" + + # Process a directory collecting DISKSPD results + Performance Counters to a property bag + + # Row reduction - table of per-vm results, optionally dropped to TSV + $row = @(Get-ChildItem $resultPat | ReduceDISKSPDResultToRowResult) + + if ($row.Count -eq 0) + { + Write-Error "No results found in directory: $ResultDirectory" + return + } + + if ($PerVMResultFile.Length) + { + $row | Export-Csv -NoTypeInformation -Delimiter "`t" -Path (Join-Path $ResultDirectory $PerVMResultFile) + } + + # + # Now summarize per-vm rows to property bag and add performance counter properties for final return + # + + $props = $row | ReduceRowResultToSummary + + # get central seconds for the performance counters + + # get average cpu utilization for each node + # get average of all nodes + + $avcpu = $(Get-ChildItem $perfctrPat |% { + + # SumProduct of total run time and processor performance is the normalized total CPU utilization + # Note each ctr% is in the range (decimal) 0-100, so we must remove the extra factor of 100 to + # wind up with a value 0-100 + ($cpu) = Get-PerformanceCounter @intervalParam -Blg $_ -SumProduct -Ctrs '\Hyper-V Hypervisor Logical Processor(_Total)\% Total Run Time','\Processor Information(_Total)\% Processor Performance' + $cpu/100 + + } | Measure-Object -Average).Average + + # Get average CSV data for central seconds, all nodes + # Note we must SumProduct average latency and iops per node to get total latency + # and then divide by total iops to get whole-cluster average. + + $t_csvr = 0 + $t_csvw = 0 + $t_csvrb = 0 + $t_csvwb = 0 + $t_csvrlat = 0 + $t_csvwlat = 0 + + Get-ChildItem $perfctrPat |% { + + # units of average total seconds of latency/second + ($csvrlat) = Get-PerformanceCounter @intervalParam -Blg $_ -SumProduct -Ctrs '\Cluster CSVFS(_Total)\avg. sec/read','\Cluster CSVFS(_Total)\reads/sec' + ($csvwlat) = Get-PerformanceCounter @intervalParam -Blg $_ -SumProduct -Ctrs '\Cluster CSVFS(_Total)\avg. sec/write','\Cluster CSVFS(_Total)\writes/sec' + + # average iops + ($csvr, $csvw, $csvrb, $csvwb) = Get-PerformanceCounter @intervalParam -Blg $_ -Ctrs '\Cluster CSVFS(_Total)\reads/sec','\Cluster CSVFS(_Total)\writes/sec','\Cluster CSVFS(_Total)\read bytes/sec','\Cluster CSVFS(_Total)\write bytes/sec' + + # LogOutput -IsVb ("$($_.BaseName) : aggregate csv read latency/s {0:N3} csv write latency/s {1:N3} csv read/s {2:N0} csv write/s {3:N0}" -f $csvrlat, $csvwlat, $csvr, $csvw) + + # aggregate totals across cluster: iops and aggregate latency/second + $t_csvr += $csvr + $t_csvw += $csvw + $t_csvrb += $csvrb + $t_csvwb += $csvwb + $t_csvrlat += $csvrlat + $t_csvwlat += $csvwlat + } + + # convert total latency to average latency/io + if ($t_csvr -ne 0) + { + $t_csvrlat /= $t_csvr + } + if ($t_csvw -ne 0) + { + $t_csvwlat /= $t_csvw + } + + # + # Add to bag and return + # Normalize latency to milliseconds for consistency with DISKSPD + # + $props["AverageCPU"] = $avcpu + $props["AverageCSVReadMilliseconds"] = $t_csvrlat * 1000 + $props["AverageCSVWriteMilliseconds"] = $t_csvwlat * 1000 + $props["AverageCSVReadIOPS"] = $t_csvr + $props["AverageCSVWriteIOPS"] = $t_csvw + $props["AverageCSVReadBW"] = $t_csvrb + $props["AverageCSVWriteBW"] = $t_csvwb + $props["AverageCSVIOPS"] = $t_csvr + $t_csvw + + return $props +} + +function ConvertToOrderedObject +{ + [CmdletBinding(DefaultParameterSetName = "Properties")] + param( + [Parameter( + ParameterSetName = "Properties", + Mandatory = $true + )] + [hashtable] + $Properties, + + [Parameter( + ParameterSetName = "InputObject", + Mandatory = $true + )] + [PSCustomObject] + $InputObject, + + [Parameter()] + [string[]] + $LeftColumn + ) + + # This wraps conversion of a property bag or a generic custom object so that + # the enumeration order of the properties on the resulting new custom object is + # defined for later operations (Export-CSV in particular). + # + # Insertion order to the ordered hashtable defines the result. + + $oh = [ordered] @{} + + switch ($PSCmdlet.ParameterSetName) + { + "Properties" + { + # specific columns in defined order ("left" in csv column sense) + foreach ($k in $LeftColumn) + { + $oh[$k] = $Properties[$k] + } + + # remainder lexically + foreach ($k in $Properties.Keys | Sort-Object) + { + if (-not $oh.Contains($k)) + { + $oh[$k] = $Properties[$k] + } + } + break + } + + "InputObject" + { + # left columns + foreach ($k in $LeftColumn) + { + $oh[$k] = $InputObject.$k + } + + # all note properties + foreach ($k in ($InputObject | Get-Member |? MemberType -eq NoteProperty).Name | Sort-Object) + { + if (-not $oh.Contains($k)) + { + $oh[$k] = $InputObject.$k + } + } + break + } + } + + [PSCustomObject] $oh +} + +function IsWithin( + [double] + $Base, + + [double] + $Target, + + [ValidateRange(1,50)] + [double] + $Percentage + ) +{ + return ([math]::abs($Target/$Base - 1) -le ($Percentage/100)) +} + +function IsAbove( + [double] + $Base, + + [double] + $Target, + + [ValidateRange(1,50)] + [double] + $Percentage + ) +{ + return (($Target/$Base - 1) -gt ($Percentage/100)) +} + +function Measure-FleetCoreWorkload +{ + [CmdletBinding()] + param( + [Parameter()] + [string] + $Cluster = ".", + + [Parameter()] + [string] + $ResultDirectory, + + [Parameter()] + [string] + $LogFile = "coreworkload.log", + + # Note: passing of an [ordered] table is not possible + # with a type specification. Type is manually validated, + # allowing for a plain unordered hashtable as well. + [Parameter()] + $KeyColumn, + + [Parameter()] + [uint32] + $Warmup = 300, + + [Parameter()] + [switch] + $SkipSddcDiagInfo, + + [Parameter()] + [switch] + $UseStorageQos + ) + + # Remoting parametersets + $clusterParam = @{} + CopyKeyIf $PSBoundParameters $clusterParam 'Cluster' + + $runParam = @{} + CopyKeyIf $PSBoundParameters $runParam 'Cluster' + CopyKeyIf $PSBoundParameters $runParam 'UseStorageQos' + + # If SddcDiagInfo is not skipped, absence of the command is an error + if (-not $SkipSddcDiagInfo -and -not (Get-Command Get-SddcDiagnosticInfo)) + { + throw "The PrivateCloud.DiagnosticInfo module must be present in order for SddcDiagnosticInfo to be gathered (Get-SddcDiagnosticInfo). Please install the module and retry, or use -SkipSddcDiagInfo to omit." + } + + $resultPath = Get-FleetPath -PathType Result @clusterParam + + # place result files in result directory unless a qualified path is provided + if (-not $LogFile.Contains('\')) { + $LogFile = Join-Path $resultPath $LogFile + } + + $myKeyColumn = $null + if ($PSBoundParameters.ContainsKey('KeyColumn')) + { + # Clone KeyColumn to allow non-modifying insert of ordered workload name / metadata columns + $myKeyColumn = [ordered] @{} + if ($KeyColumn.GetType().Name -eq 'OrderedDictionary') + { + foreach ($k in $KeyColumn.Keys) + { + $myKeyColumn[$k] = $KeyColumn[$k] + } + } + elseif ($KeyColumn.GetType().Name -eq 'Hashtable') + { + # provide lexical order of unordered input columns + foreach ($k in $KeyColumn.Keys | Sort-Object) + { + $myKeyColumn[$k] = $KeyColumn[$k] + } + } + else + { + Write-Error "KeyColumn must be a regular unordered or [ordered] hashtable." + return + } + } + else + { + $myKeyColumn = @{} + } + + $runParam.KeyColumn = $myKeyColumn + + # Inner loop warmup is min of specified or 30s. This is the time spent warming the + # search iterations during workload sweeps after full warmup has occured. + if ($Warmup -lt 30) + { + $SearchWarmup = $Warmup + } + else + { + $SearchWarmup = 30 + } + + # + # Apply values first to keycolumns and then for running configuration (iff required for run - see runners) + # + + function ApplyVMTemplate + { + param( + [string] + $ComputeTemplate, + + [double] + $VMPercent = 100, + + [switch] + $KeyColumn + ) + + if ($KeyColumn) + { + $spec = Get-FleetComputeTemplate -ComputeTemplate $ComputeTemplate + $myKeyColumn.ComputeTemplate = $ComputeTemplate + $myKeyColumn.ProcessorCount = $spec.ProcessorCount + $myKeyColumn.MemoryStartupBytes = $spec.MemoryStartupBytes + $myKeyColumn.FleetVMPercent = $VMPercent + } + else + { Set-Fleet @clusterParam -ComputeTemplate $myKeyColumn.ComputeTemplate } + } + + function ApplyVMDataDisk + { + param( + [switch] + $KeyColumn + ) + + if ($KeyColumn) + { + $dataDiskSize = Get-FleetDataDiskEstimate @clusterParam -CachePercent 200 -CapacityPercent 30 -VMPercent $myKeyColumn.FleetVMPercent -Verbose + + # Opt to use internal load file if disk size would be smaller. + # This can happen in storage limited/compute rich builds. + if ($dataDiskSize -le 10GB) + { + $dataDiskSize = 0 + } + $myKeyColumn.DataDiskBytes = $dataDiskSize + } + else + { + $d = Set-Fleet -DataDiskSize $myKeyColumn.DataDiskBytes -Running + + # If data disks were newly created, execute write warmup. + if ($null -ne $d) + { + Start-FleetWriteWarmup -VMWorkingSetSize $myKeyColumn.DataDiskBytes + } + } + } + + function StartVM + { + if ($state.ContainsKey('startVM')) { return } + $state.startVM = $true + + AlignVM + ApplyVMTemplate $myKeyColumn.ComputeTemplate $myKeyColumn.FleetVMPercent + Start-Fleet @clusterParam -Percent $myKeyColumn.FleetVMPercent + ApplyVMDataDisk + } + + function AlignVM + { + param( + [switch] + $IfStarted + ) + + if ($IfStarted -and -not $state.ContainsKey('startVM')) { return } + + Move-Fleet @clusterParam -DistributeVMPercent (100 - $myKeyColumn.VMAlignmentPct) -VMPercent $myKeyColumn.FleetVMPercent + Repair-Fleet @clusterParam + } + + function ReadWarmup + { + param( + [xml] + $ProfileXml + ) + + if ($myKeyColumn.DataDiskBytes -eq 0) { + $sz = 10GB + } + else + { + $sz = $myKeyColumn.DataDiskBytes + } + + Start-FleetReadCacheWarmup @clusterParam -ProfileXml $ProfileXml -VMWorkingSetSize $sz -Verbose + } + + # + # Per VM-configuration runners + # + # VM start/move and (re)configuration is delayed until we discover a result which has not been + # produced yet. This allows for low impact & idempotent restart - for instance, if all + # results with a given configuration have already been produced. This becomes important + # since the common case for restart will be that an error was encountered and we want to + # restart at the existing configuration (after validating, quickly, that all prior are done). + # + # Move must be evaluated every time alignment logically changes. Move only needs to happen if + # VMs are started, however. + # + + function RunInner1VCPUMatrix + { + $once = $true + foreach ($w in 0, 10, 30) + { + $myKeyColumn.Workload = "General4KWriteRatio$w" + $ProfileXml = Get-FleetProfileXml -Name General -Warmup $Warmup -Duration 60 -Cooldown 30 -WriteRatio $w -ThreadsPerTarget 1 -BlockSize 4KB -Random -RandomRatio 50 -RequestCount 32 + if (-not (Test-FleetResultRun @clusterParam -KeyColumn $myKeyColumn)) { + StartVM + if ($once) + { + $once = $false + ReadWarmup -ProfileXml $ProfileXml + } + Start-FleetResultRun @runParam -ProfileXml $ProfileXml -CpuSweep -SearchWarmup $SearchWarmup + } + } + + # Peak with 5GB workingset + $myKeyColumn.Workload = "Peak4kRead" + $ProfileXml = Get-FleetProfileXml -Name Peak -Warmup $Warmup -Duration 60 -Cooldown 30 -MaxOffset 5GB -WriteRatio 0 -ThreadsPerTarget 1 -BlockSize 4KB -Random -RequestCount 32 + if (-not (Test-FleetResultRun @clusterParam -KeyColumn $myKeyColumn)) { + StartVM + ReadWarmup -ProfileXml $ProfileXml + Start-FleetResultRun @runParam -ProfileXml $ProfileXml + } + + $myKeyColumn.Workload = "Vdi" + $ProfileXml = Get-FleetProfileXml -Name Vdi -Warmup $Warmup -Duration 60 -Cooldown 30 + if (-not (Test-FleetResultRun @clusterParam -KeyColumn $myKeyColumn)) { + StartVM + ReadWarmup -ProfileXml $ProfileXml + Start-FleetResultRun @runParam -ProfileXml $ProfileXml -CpuSweep -SearchWarmup $SearchWarmup -CutoffAverageCPU 40 -CutoffAverageLatencyMs 3 + } + } + + function RunInner4VCPUMatrix + { + $myKeyColumn.Workload = "Sql" + $ProfileXml = Get-FleetProfileXml -Name Sql -Warmup $Warmup -Duration 60 -Cooldown 30 + if (-not (Test-FleetResultRun @clusterParam -KeyColumn $myKeyColumn)) { + StartVM + ReadWarmup -ProfileXml $ProfileXml + Start-FleetResultRun @runParam -ProfileXml $ProfileXml -CpuSweep -SearchWarmup $SearchWarmup -CutoffAverageCPU 40 -CutoffAverageLatencyMs 3 + } + } + + try + { + $originalEAP = $null + $originalPowerScheme = $null + + Start-Transcript -Path $LogFile -Append + + $originalEAP = $ErrorActionPreference + $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop + + # Show versioning up top; delay exporting until creation of the final packaging to + # avoid interference from result sweeps/etc. + Get-FleetVersion @clusterParam |% { + "Version: $($_.Component) $($_.Version)" + } + + $originalPowerScheme = Get-FleetPowerScheme @clusterParam + Set-FleetPowerScheme @clusterParam -Scheme HighPerformance + $myKeyColumn.PowerScheme = 'HighPerformance' + + # + # VM Config @ A1v2 + # + + $state = @{} + Stop-Fleet @clusterParam + ApplyVMTemplate -KeyColumn A1v2 100 + ApplyVMDataDisk -KeyColumn + + foreach ($alignment in 100,70) + { + $myKeyColumn.VMAlignmentPct = $alignment + AlignVM -IfStarted + RunInner1VCPUMatrix + } + + # + # VM Config @ A4v2 + # + + $state = @{} + Stop-Fleet @clusterParam + ApplyVMTemplate -KeyColumn A4v2 25 + ApplyVMDataDisk -KeyColumn + + foreach ($alignment in 100,70) + { + $myKeyColumn.VMAlignmentPct = $alignment + AlignVM -IfStarted + RunInner4VCPUMatrix + } + } + finally + { + if ($null -ne $originalEAP) { $ErrorActionPreference = $originalEAP } + if ($null -ne $originalPowerScheme) { Set-FleetPowerScheme @clusterParam -Scheme $originalPowerScheme } + Stop-Transcript + } + + # + # Add Get-Sddc? + # + + if (-not $SkipSddcDiagInfo -and -not (Test-Path (Join-Path $resultPath "HealthTest*"))) + { + LogOutput "Gathering SddcDiagnosticInfo to document running environment" + + # Six hours should be sufficient for all usual purposes, slim down capture size + Get-SddcDiagnosticInfo @clusterParam -ZipPrefix (Join-Path $resultPath "HealthTest") -HoursOfEvents 6 + } + + # + # Add additional metadata for the run (versioning) + # + + Get-FleetVersion | Export-Clixml (Join-Path $resultPath "version.xml") + + # + # Now create the final packaging + # + + if ($PSBoundParameters.ContainsKey('ResultDirectory')) + { + $finalResult = (Join-Path $ResultDirectory "result-coreworkload-") + } + else + { + # Note: file in same directory as the result directory (...\collect\result-coreworkload-xyz.zip) + $finalResult = $resultPath + "-coreworkload-" + } + + $finalResult += (Get-Date).ToString('yyyyMMdd-HHmm') + $finalResult += ".zip" + + LogOutput "Compressing final result archive: $finalResult" + Compress-Archive -Path $resultPath\* -DestinationPath $finalResult -ErrorAction Stop + + # + # Bounce CSV to workaround very itermittent issue where a latent handle prevents BLG deletion. + # TBD root cause. + # + + $null = Get-ClusterSharedVolume @clusterParam |? Name -match $collectVolumeName | Move-ClusterSharedVolume + + LogOutput "Clearing result directory: $resultPath" + Remove-Item $resultPath\* -Recurse + + LogOutput -ForegroundColor Green "DONE: final result archive $finalResult" +} + +function Get-FleetResultLog +{ + [CmdletBinding()] + param( + [string] + $ResultLog, + + # Note: passing of an [ordered] table is not possible + # with a type specification. Type is manually validated, + # allowing for a plain unordered hashtable as well. + [Parameter()] + $KeyColumn + ) + + function Typeify + { + param( + [Parameter(ValueFromPipeline = $true)] + $InputObject + ) + + process { + + # Convert all properties which appear to be numeric to actual numerics. + # Use 64bit integers for lack of a better estimation of a safe default + # type that will not overflow if/when operated on. + # + # Convert all properties which appear to be booleans to actual booleans. + + foreach ($prop in (Get-Member -InputObject $InputObject -MemberType NoteProperty).Name) + { + switch -regex ($InputObject.$prop) + { + '^\d+$' { + $InputObject.$prop = [uint64] $InputObject.$prop + } + '^-\d+$' { + $InputObject.$prop = [int64] $InputObject.$prop + } + '^-?\d+\.\d+$' { + $InputObject.$prop = [double] $InputObject.$prop + } + '^True$' { + $InputObject.$prop = $true + } + '^False$' { + $InputObject.$prop = $false + } + } + } + + $InputObject + } + } + + if ($PSBoundParameters.ContainsKey('KeyColumn') -and + (-not ($KeyColumn.GetType().Name -eq 'OrderedDictionary' -or + $KeyColumn.GetType().Name -eq 'Hashtable'))) + { + Write-Error "KeyColumn must be a regular unordered or [ordered] hashtable." + return + } + + if ($PSBoundParameters.ContainsKey('KeyColumn')) + { + Import-Csv $ResultLog -Delimiter "`t" | FilterObject -Filter $KeyColumn | Typeify + } + else + { + Import-Csv $ResultLog -Delimiter "`t" | Typeify + } +} + +function Test-FleetResultRun +{ + [CmdletBinding()] + param( + [Parameter()] + [string] + $Cluster = ".", + + [Parameter()] + [string] + $ResultLog = "result-log.tsv", + + # Note: passing of an [ordered] table is not possible + # with a type specification. Type is manually validated, + # allowing for a plain unordered hashtable as well. + [Parameter(Mandatory = $true)] + $KeyColumn + ) + + # Remoting parametersets + $clusterParam = @{} + CopyKeyIf $PSBoundParameters $clusterParam 'Cluster' + + if (-not $ResultLog.Contains('\')) { + + # Note we only need the resultpath if not fuilly qualified + $resultPath = Get-FleetPath -PathType Result @clusterParam + $ResultLog = Join-Path $resultPath $ResultLog + } + + if (-not (Test-Path $ResultLog)) + { + return $false + } + + $n = 0 + Get-FleetResultLog -ResultLog $ResultLog -KeyColumn $KeyColumn |% { $n += 1 } + + # If any - there may be a ? if we match multiple (incomplete keys?), but there may be utility there. + return ($n -ne 0) +} + +# Generates and sets up the run profile XML file for a given workload profile +# Can be used for free runs +function Set-FleetRunProfileScript +{ + param( + [string] + $Cluster = ".", + + [Parameter(Mandatory=$true)] + [xml] + $ProfileXml, + + [switch] + $CpuSweep, + + [switch] + $UseStorageQos, + + [uint32] + $SearchWarmup = 0, + + [string] + $RunFile = "sweep.ps1", + + [string] + $RunProfileFile = "sweep.xml" + ) + + $clusterParam = @{} + CopyKeyIf $PSBoundParameters $clusterParam 'Cluster' + + if ($CpuSweep -and -not (IsProfileSingleTimespan -ProfileXml $ProfileXml)) + { + Write-Error "Profile for a CpuSweep can only contain a single timespan" + return + } + + $QoS = 0 + + if (IsProfileThroughputLimited -ProfileXml $ProfileXml) + { + $addspec = "baseline" + if ($CpuSweep) + { + if(IsProfileSingleTarget -ProfileXml $ProfileXml -and $SearchWarmup) + { + $ProfileXml = $ProfileXml | Set-FleetProfile -Warmup $SearchWarmup + } + } + } + else + { + $addspec = "qos$qos" + if ($CpuSweep) + { + if ($UseStorageQos) + { + Set-StorageQosPolicy -Name SweepQoS -MaximumIops $QoS @cimParam + } + else + { + $ProfileXml = $ProfileXml | Set-FleetProfile -Throughput $QoS -ThroughputUnit IOPS + } + } + } + + $accessNode = Get-AccessNode @clusterParam + $cimParam = @{ CimSession = $accessNode } + + if ($VerbosePreference) + { + $ProfileXml | Convert-FleetXmlToString |% { LogOutput -IsVb $_ } + } + + $Duration = GetProfileDuration -ProfileXml $ProfileXml -Total + + if ($null -eq $Duration -or $Duration -eq 0) + { + Write-Error "Run duration could not be determined, cannot continue" + return + } + + $controlPath = Get-FleetPath -PathType Control @clusterParam + $runProfileFilePath = Join-Path $controlPath $RunProfileFile + Remove-Item $runProfileFilePath -Force -ErrorAction SilentlyContinue + $ProfileXml | Convert-FleetXmlToString | Out-File $runProfileFilePath -Encoding ascii -Width ([int32]::MaxValue) -Force + + Create-TemplateRunScript $addspec $controlPath $RunFile $RunProfileFile +} + +function Start-FleetResultRun +{ + [CmdletBinding(DefaultParameterSetName = 'Default')] + param( + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'CpuSweep')] + [Parameter(ParameterSetName = 'CpuSweepCombinedLatency')] + [string] + $Cluster = ".", + + [Parameter(ParameterSetName = 'Default', Mandatory = $true)] + [Parameter(ParameterSetName = 'CpuSweep', Mandatory = $true)] + [Parameter(ParameterSetName = 'CpuSweepCombinedLatency', Mandatory = $true)] + [xml] + $ProfileXml, + + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'CpuSweep')] + [Parameter(ParameterSetName = 'CpuSweepCombinedLatency')] + [string] + $ResultFile = "result.tsv", + + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'CpuSweep')] + [Parameter(ParameterSetName = 'CpuSweepCombinedLatency')] + [string] + $ResultLog = "result-log.tsv", + + # Note: passing of an [ordered] table is not possible + # with a type specification. Type is manually validated, + # allowing for a plain unordered hashtable as well. + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'CpuSweep')] + [Parameter(ParameterSetName = 'CpuSweepCombinedLatency')] + $KeyColumn, + + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'CpuSweep')] + [Parameter(ParameterSetName = 'CpuSweepCombinedLatency')] + [string] + $RunLabel, + + [Parameter(ParameterSetName = 'CpuSweep', Mandatory = $true)] + [Parameter(ParameterSetName = 'CpuSweepCombinedLatency', Mandatory = $true)] + [switch] + $CpuSweep, + + [Parameter(ParameterSetName = 'CpuSweep')] + [Parameter(ParameterSetName = 'CpuSweepCombinedLatency')] + [uint32] + $SearchWarmup = 0, + + [Parameter(ParameterSetName = 'CpuSweep')] + [Parameter(ParameterSetName = 'CpuSweepCombinedLatency')] + [switch] + $UseStorageQos, + + [Parameter(ParameterSetName = 'CpuSweep')] + [Parameter(ParameterSetName = 'CpuSweepCombinedLatency')] + [ValidateRange(1,100)] + [double] + $CutoffAverageCPU, + + # Latency cutoffs - ranges are arbitrarily chosen to loosely bracket + # reasonable values : 10us - 1s + [Parameter(ParameterSetName = 'CpuSweep')] + [ValidateRange(0.01,1000)] + [double] + $CutoffAverageReadLatencyMs, + + [Parameter(ParameterSetName = 'CpuSweep')] + [ValidateRange(0.01,1000)] + [double] + $CutoffAverageWriteLatencyMs, + + [Parameter(ParameterSetName = 'CpuSweepCombinedLatency', Mandatory = $true)] + [ValidateRange(0.01,1000)] + [double] + $CutoffAverageLatencyMs + ) + + # Remoting parametersets + $clusterParam = @{} + CopyKeyIf $PSBoundParameters $clusterParam 'Cluster' + + # + # Validate + # + # CpuSweep only applies to a single timespan; we do not currently reason about averaging results + # through multiple timespans. + # + + if ($CpuSweep -and -not (IsProfileSingleTimespan -ProfileXml $ProfileXml)) + { + Write-Error "Profile for a CpuSweep can only contain a single timespan" + return + } + + # Produce ordered set of key columns + $orderedKeyColumn = [ordered] @{} + if ($PSBoundParameters.ContainsKey('KeyColumn')) + { + if ($KeyColumn.GetType().Name -eq 'OrderedDictionary') + { + $orderedKeyColumn = $KeyColumn + } + elseif ($KeyColumn.GetType().Name -eq 'Hashtable') + { + $orderedKeyColumn = [ordered] @{} + foreach ($k in $KeyColumn.Keys | Sort-Object) + { + $orderedKeyColumn[$k] = $KeyColumn[$k] + } + } + else + { + Write-Error "KeyColumn must be a regular unordered or [ordered] hashtable." + return + } + } + + # Already complete? + if ($orderedKeyColumn.Count -and (Test-FleetResultRun @clusterParam -ResultLog $ResultLog -KeyColumn $KeyColumn)) + { + LogOutput -ForegroundColor Cyan "Run already measured for $($orderedKeyColumn.Keys |% { $_, $orderedKeyColumn[$_] -join '=' })" + return + } + + $accessNode = Get-AccessNode @clusterParam + $cimParam = @{ CimSession = $accessNode } + + $resultPath = Get-FleetPath -PathType Result @clusterParam + + # place result files in result directory unless a qualified path is provided + if (-not $ResultFile.Contains('\')) { + $ResultFile = Join-Path $resultPath $ResultFile + } + if (-not $ResultLog.Contains('\')) { + $ResultLog = Join-Path $resultPath $ResultLog + } + + # + # Expected counter set for post-processing. + # + + $pc = @('\Hyper-V Hypervisor Logical Processor(_Total)\% Total Run Time', + '\Processor Information(_Total)\% Processor Performance', + '\Cluster CSVFS(_Total)\reads/sec', + '\Cluster CSVFS(_Total)\avg. sec/read', + '\Cluster CSVFS(_Total)\writes/sec', + '\Cluster CSVFS(_Total)\avg. sec/write', + '\Cluster CSVFS(_Total)\read bytes/sec', + '\Cluster CSVFS(_Total)\write bytes/sec') + + # Get timing for post processing of data + $Warmup, $Duration = GetProfileDuration -ProfileXml $ProfileXml -Warmup -Duration + + # Limit qos infill search, done if the next best gap is closer than %age + $qosWindow = 8 + + # Limit qos adaptive upper anchor search, done if the gap closes within this %age of upper cutoff + $qosAdaptiveWindow = 2 + + # Percent variance of result IOPS <=> QoS target which will result in a scale/surge cutoff + $qosScaleWindow = 1.5 + + function GetIterationFiles + { + param( + [string] + $resultPath + ) + + # per-vm tsv for iteration + Get-ChildItem $resultPath\* -File -Include result-vm-*.tsv + # all other, excluding main tsv and log + Get-ChildItem $resultPath\* -File -Exclude *.tsv,*.log + } + + # Dynamic scope wrapper for inner loop running the iteration itself + function DoIteration + { + param( + [int] + $QoS = 0, + + [switch] + $IsSearch, + + [switch] + $IsAdaptive, + + [switch] + $IsBaseline + ) + + # + # Measure once. This functions as the short-circuit for the fixed + # QoS bracketing of the sweep. Baselines are simply that. Non + # baselines should not consider the baseline, which will be at + # QoS "zero" (and should not prevent an unconstrained run of a + # baselined profile). + # + # This is also where we decide on the filename labeling for this run + # (if we make it). + # + + if ($IsBaseline) + { + $prior = @($results |? RunType -eq ([RunType]::Baseline)) + $addspec = "baseline" + } + else + { + $prior = @($results |? RunType -ne ([RunType]::Baseline) |? QOSperVM -eq $QoS) + $addspec = "qos$qos" + } + + if ($prior.Count -eq 1) + { + LogOutput "Result already captured" + return $prior + } + elseif ($prior.Count -ne 0) + { + throw "Unexpected multiple measurements @ QoS $QoS ($($prior.Count) total) in $ResultFile - please inspect" + } + + # If this is a sweep and not an initial throughput baseline, apply QoS. + # Note sweeps can start with a profile that has no throughput constraints + # as well as ones that do (the latter are "baselines", the former are simply + # unconstrained). + if ($CpuSweep -and -not $IsBaseline) + { + if ($UseStorageQos) + { + Set-StorageQosPolicy -Name SweepQoS -MaximumIops $QoS @cimParam + } + else + { + $ProfileXml = $ProfileXml | Set-FleetProfile -Throughput $QoS -ThroughputUnit IOPS + } + + if ($IsSearch -and $SearchWarmup) + { + $ProfileXml = $ProfileXml | Set-FleetProfile -Warmup $SearchWarmup + $Warmup = $SearchWarmup + } + } + + if ($VerbosePreference) + { + $ProfileXml | Convert-FleetXmlToString |% { LogOutput -IsVb $_ } + } + + # Use thread autoscaling? + Start-FleetRun @clusterParam -pc $pc -SampleInterval 1 -ProfileXml $ProfileXml -AddSpec $addspec + + # Get result property bag and add sweep metadata columns, export. + $props = ProcessDISKSPDResult -ResultDirectory $resultPath -Warmup $Warmup -d $Duration -AddSpec $addspec -PerVMResultFile "result-vm-$addspec.tsv" + + # + # Gather results for this iteration + # + + if ($RunLabel.Length) + { + $dest = Join-Path $resultPath $RunLabel + $null = mkdir $dest -ErrorAction SilentlyContinue + if (-not (Test-Path $dest)) + { + throw "Could not create run result gather directory $dest" + } + GetIterationFiles $resultPath | Move-Item -Destination $dest + + LogOutput Gathered results to $dest + } + + # Insert key columns as metadata for the result table, ending with our QOS. + # Note that all results in a given result file MUST share the same key columns. + + foreach ($k in $orderedKeyColumn.Keys) + { + $props[$k] = $orderedKeyColumn[$k] + } + + # + # Evaluate cutoffs + # + + $cutoff = [CutoffType]::No + + # Surge is a special case where tooling has malfunctioned/errored in some way to run faster than target + if ($PSBoundParameters.ContainsKey('QoS') -and $QoS -ne 0 -and (IsAbove ($QoS * $vms) $props.IOPS $qosScaleWindow)) + { + LogOutput -ForegroundColor Red ('CUTOFF SURGE: {0:N0} IOPS is > {1:P1} above target {2:N0} IOPS (QoS {3:N0} from {4:N0} VMs)' -f ($props.IOPS,($qosScaleWindow/100),($QoS*$vms),$QoS,$vms)) + $cutoff = [CutoffType]::Surge + } + + # Fail to scale - since we checked surge, this will always be slower-than target + elseif ($PSBoundParameters.ContainsKey('QoS') -and $QoS -ne 0 -and -not (IsWithin ($QoS * $vms) $props.IOPS $qosScaleWindow)) + { + LogOutput -ForegroundColor Cyan ('CUTOFF SCALE: {0:N0} IOPS is > {1:P1} off of target {2:N0} IOPS (QoS {3:N0} from {4:N0} VMs)' -f ($props.IOPS,($qosScaleWindow/100),($QoS*$vms),$QoS,$vms)) + $cutoff = [CutoffType]::Scale + } + + # CPU utilizati0on cutoff + elseif ($CutoffAverageCPU -ne 0 -and $CutoffAverageCPU -lt $props.AverageCPU) + { + LogOutput -ForegroundColor Cyan ('CUTOFF CPU: {0:P1} CPU utilization is > {1:P1} cutoff limit' -f (($props.AverageCPU/100),($CutoffAverageCPU/100))) + $cutoff = [CutoffType]::CPU + } + + # + # Single shared latency cutoff + # + + elseif ($CutoffAverageLatencyMs -ne 0 -and + $CutoffAverageLatencyMs -lt $props.AverageReadMilliseconds) + { + LogOutput -ForegroundColor Cyan ('CUTOFF COMBINED LATENCY: read latency {0:N3}ms > {1:N3}ms cutoff limit' -f ($props.AverageReadMilliseconds,$CutoffAverageLatencyMs)) + $cutoff = [CutoffType]::ReadLatency + } + + elseif ($CutoffAverageLatencyMs -ne 0 -and + $CutoffAverageLatencyMs -lt $props.AverageWriteMilliseconds) + { + LogOutput -ForegroundColor Cyan ('CUTOFF COMBINED LATENCY: write latency {0:N3}ms > {1:N3}ms cutoff limit' -f ($props.AverageWriteMilliseconds,$CutoffAverageLatencyMs)) + $cutoff = [CutoffType]::WriteLatency + } + + # + # Individual latency cutoffs + # + + elseif ($CutoffAverageReadLatencyMs -ne 0 -and + $CutoffAverageReadLatencyMs -lt $props.AverageReadMilliseconds) + { + LogOutput -ForegroundColor Cyan ('CUTOFF READ LATENCY: read latency {0:N3}ms > {1:N3}ms cutoff limit' -f ($props.AverageReadMilliseconds,$CutoffAverageReadLatencyMs)) + $cutoff = [CutoffType]::ReadLatency + } + + elseif ($CutoffAverageWriteLatencyMs -ne 0 -and + $CutoffAverageWriteLatencyMs -lt $props.AverageWriteMilliseconds) + { + LogOutput -ForegroundColor Cyan ('CUTOFF WRITE LATENCY: write latency {0:N3}ms > {1:N3}ms cutoff limit' -f ($props.AverageWriteMilliseconds,$CutoffAverageWriteLatencyMs)) + $cutoff = [CutoffType]::WriteLatency + } + + # Add in metadata columns + # CAREFUL: [switch] parameters are a distinct object + if ($IsBaseline.IsPresent) + { + $props.RunType = [RunType]::Baseline + } + elseif ($IsAdaptive.IsPresent) + { + $props.RunType = [RunType]::AnchorSearch + } + else + { + $props.RunType = [RunType]::Default + } + $props.CutoffType = $cutoff + $props.QOSperVM = $QoS + $props.VMCount = $vms + + # Add in optional column(s) + $optColumn = @() + + if ($RunLabel.Length) + { + $props.RunLabel = $RunLabel + $optColumn += 'RunLabel' + } + + # Add to result file and in-memory table, moving metadata (visuals) to the left. Swallow the index of the addded result. + $o = ConvertToOrderedObject -Properties $props -LeftColumn ($optColumn + $orderedKeyColumn.Keys + 'RunType' + 'CutoffType' + 'QOSperVM' + 'VMCount' + 'IOPS') + $o | Export-Csv -NoTypeInformation -Delimiter "`t" -Path $ResultFile -Append -ErrorAction Stop + + $null = $results.Add($o) + + # return current result + return $o + } + + function LogToComplete + { + # No run label => no log (single result run) + if ($RunLabel.Length -eq 0) { return } + + $props = @{} + + foreach ($k in $orderedKeyColumn.Keys) + { + $props[$k] = $orderedKeyColumn[$k] + } + + $props.RunLabel = $RunLabel + $props.Complete = 1 + + # RunLabel on the far left + ConvertToOrderedObject -Properties $props -LeftColumn (@('RunLabel') + $orderedKeyColumn.Keys) | + Export-Csv -NoTypeInformation -Delimiter "`t" -Path $ResultLog -Append -ErrorAction Stop + } + + try + { + # if using in-stack qos mechanism, make qos policy and apply to vms + if ($UseStorageQos) + { + $qosName = 'SweepQoS' + Get-StorageQosPolicy -Name $qosName @cimParam -ErrorAction SilentlyContinue | Remove-StorageQosPolicy -Confirm:$false + $null = New-StorageQosPolicy -Name $qosName -PolicyType Dedicated @cimParam + Set-FleetQos -Name $qosName @clusterParam + } + else + { + # clear any applied in-stack qos + Set-FleetQos @clusterParam + } + + # + # Accumulate iteration result objects for fit. Preload from existing data (if any) so that + # we do not repeat steps. Note that empty key column is a null filter, passing everything. + # + + $results = [collections.arraylist] @() + if (Test-Path $ResultFile) + { + Get-FleetResultLog -ResultLog $ResultFile -KeyColumn $orderedKeyColumn |% { $null = $results.Add($_) } + } + + # + # Use pre-existing iteration's runlabel else generate new guid-based label if none specified. + # + + if ($results.Count) + { + if (Get-Member -InputObject $results[0] -Name RunLabel) + { + $RunLabel = $results[0].RunLabel + } + } + elseif (-not $PSBoundParameters.ContainsKey('RunLabel') -and $orderedKeyColumn.Count) + { + $RunLabel = [System.Guid]::NewGuid().Guid + } + + # + # Log run keys if present + # + + if ($orderedKeyColumn.Count) + { + LogOutput -ForegroundColor Cyan "Starting run for $($orderedKeyColumn.Keys |% { $_, $orderedKeyColumn[$_] -join '=' })" + } + + # + # Count the number of online vms in the configuration so we can reason about IOPS -> QoS relationships. + # + + $vms = @(Get-ClusterGroup @clusterParam |? GroupType -eq VirtualMachine |? Name -like 'vm-*' |? State -eq Online).Count + + # + # Baseline throughput limited profiles else find unconstrained/default upper anchor. + # A baselined run can start a cpusweep loop, following up with the adaptive search to find its upper anchor. + # Unconstrained will start with normal infill. + # + + $sweepAdaptive = $false + $doUnconstrained = $false + $paramUnconstrained = @{ QoS = 0 } + + if (IsProfileThroughputLimited -ProfileXml $ProfileXml) + { + $o = DoIteration -IsBaseline + LogOutput -ForegroundColor Cyan ("RESULT: baseline {0:N0} IOPS @ {1:P1} CPU => {2:N0} IOPS/VM from {3:N0} VMs" -f ($o.IOPS,($o.AverageCPU/100),($o.IOPS/$vms),$vms)) + + # If this is a CPU Sweep, set up for the upper anchor search. + # Single target throughput limited profiles can be run unconstrained (QoS 0) to find the upper anchor. + # Multi-target throughput limited profiles require the adaptive anchor search. + # + # We'll always show the baseline above on restart (may be complete). Then, if a default/infill result + # has not been logged we know that the anchor search is needed or potentially incomplete and the loop + # should start on that side. Conversely, if there is a default/infill we know it is complete. + if ($CpuSweep) + { + if(IsProfileSingleTarget -ProfileXml $ProfileXml) + { + # Unconstrained run here is part of the search (speedy warmup - assume baseline run/restarted after) + $doUnconstrained = $true + $paramUnconstrained.IsSearch = $true + } + elseif (@($results |? RunType -eq [RunType]::Default).Count -eq 0) + { + $sweepAdaptive = $true + } + } + } + + # Default case: take the profile and run it wide open. + else + { + $doUnconstrained = $true + } + + # Note: if this is a CPU Sweep, this may be the second run of a throughput limited profile (above) + if ($doUnconstrained) + { + $o = DoIteration @paramUnconstrained + LogOutput -ForegroundColor Cyan ("RESULT: unconstrained {0:N0} IOPS @ {1:P1} CPU => {2:N0} IOPS/VM from {3:N0} VMs" -f ($o.IOPS,($o.AverageCPU/100),($o.IOPS/$vms),$vms)) + } + + # If not a sweep, this completes the run. + if (-not $CpuSweep) { + LogOutput -ForegroundColor Green "Single run, complete" + LogToComplete + return + } + + # + # Pair of points defining the current place in the sweep. Becomes valid after there are two or more measured points. + # Adaptive: uppermost points defining the adaptive search (upper may represent cutoff) + # Infill: bracketing the current split + # + + $p1 = $p2 = $null + $last = $false + + do { + + # + # Adaptive sweep for upper anchor + # + # Scale up until cutoff is reached, then iteratively split the resulting gap until within + # the given tolerance. + # + + if ($sweepAdaptive) + { + $p2, $p1 = GetUpperAnchor $results -OrderBy 'IOPS' + + # Upper (single or of two) measurement, not cutoff + if ($p2.CutoffType -eq [CutoffType]::No) + { + $qos = 5 * $p2.IOPS / $vms + LogOutput -ForegroundColor Cyan ("ADAPTIVE UP: scaling up to QoS {0:N0} => {1:N0} IOPS from {2:N0} VMs" -f ($qos,($qos*$vms),$vms)) + } + + # Upper is cutoff (single or of two). Split in between to try to close the cutoff gap. + else + { + # Determine lower side of split (either p1 or 0) + $lower = 0 + if ($null -ne $p1) + { + $lower = [int] $p1.IOPS + } + + # If upper points are already within the window, we are done now. Note that one will be the most recently measured. + if ($lower -ne 0 -and (IsWithin $p1.IOPS $p2.IOPS $qosAdaptiveWindow)) + { + LogOutput -ForegroundColor Green ("ADAPTIVE COMPLETE: gap between {1:N0} and {2:N0} IOPS is within {0:P1}" -f (($qosAdaptiveWindow/100),$p1.IOPS,$p2.IOPS)) + $sweepAdaptive = $false + continue + } + + $tgtIOPS = [int] (($p2.IOPS + $lower) / 2) + $qos = $tgtIOPS / $vms + + # Edge case where split bottoms out at an unsplittable small integer (like [2-3] with large percentage delta) + if ($tgtIOPS -eq $lower) + { + LogOutput -ForegroundColor Green ("ADAPTIVE COMPLETE: unsplittable gap between [{0:N0} - {1:N0}] IOPS" -f ($lower,$p2.IOPS)) + $sweepAdaptive = $false + continue + } + + LogOutput -ForegroundColor Cyan ("ADAPTIVE: splitting [{3:N0} - {4:N0} IOPS] to QoS {0:N0} => {1:N0} IOPS from {2:N0} VMs" -f ($qos,($qos*$vms),$vms,$lower,$p2.IOPS)) + + # If the p2 cutoff is within the window of the target, we can pull the adaptive sweep down now. + # + # There are two basoc possibilities: the system will scale onto this target or it will undershoot. + # Regardless of how it might undershoot (triggering the scale cutoff or not) there is no need + # to reason about continuing. If the undershoot did not trigger the scale cutoff and the result + # stayed outside the adaptive cutoff w.r.t. p2 we would continue pounding away. This occurs when p1 + # gets close to the actual scaling limit. + # + # Regardless of the result we'll get we do want this target attempt to try to dial in the upper + # anchor, but we know ahead of time (now) that will complete the search. + + if (IsWithin $tgtIOPS $p2.IOPS $qosAdaptiveWindow) + { + LogOutput -ForegroundColor Green ("ADAPTIVE COMPLETING: gap between {1:N0} and next target {2:N0} IOPS (QoS {3:N0}) is within {0:P1}" -f (($qosAdaptiveWindow/100),$p1.IOPS,$tgtIOPS,$qos)) + $sweepAdaptive = $false + } + } + } + + # + # Infill - anchor identified, filling in the sweep coverage. + # + # Target next QOS based on dividing the largest gap in Average CPU achieved: either splitting + # the IOPS for those measured CPU utilizations OR toward zero, if that is the largest gap in IOPS + # targeted so far. + # + + else + { + # At anchor; halve IOPS/vm to begin sweep. + if ($results.Count -eq 1) + { + $qos = $results.IOPS / 2 / $vms + LogOutput -ForegroundColor Cyan ("START: halving anchor to QoS {0:N0} => target {1:N0} IOPS from {2:N0} VMs" -f $qos,($qos*$vms),$vms) + } + + # Contine sweep at best available split. + else + { + $p2, $p1 = GetNextSplit $results 'AverageCPU' -OrderBy 'IOPS' + $minIOPS = ($results.IOPS | Measure-Object -Minimum).Minimum + + # Determine lower side of split (either p1 or 0, if p2 wound up being a cutoff) + $lower = 0 + if ($null -ne $p1) + { + $lower = [int] $p1.IOPS + } + + # Is gap based on AverageCPU split larger than the minimum? + if ($null -eq $p1 -or $minIOPS -lt ($p2.IOPS - $p1.IOPS)) + { + # Stop if this best split has met the infill goal window + if (IsWithin $p2.IOPS $lower $qosWindow) + { + LogOutput -ForegroundColor Yellow ("STOP: largest measurement gap, between {1:N0} and {2:N0} IOPS, would result in next QOS < +/-{0:P1}" -f (($qosWindow/100),$lower,$p2.IOPS)) + break + } + + # Average and scale IOPS -> VM QOS + $tgtIOPS = [int] (($p2.IOPS + $lower) / 2) + + # Edge case where split bottoms out at an unsplittable small integer (like [2-3] with large percentage delta) + if ($tgtIOPS -eq $lower) + { + LogOutput -ForegroundColor Green ("STOP: unsplittable gap between [{0:N0} - {1:N0}] IOPS" -f ($lower,$p2.IOPS)) + break + } + + $qos = $tgtIOPS / $vms + LogOutput -ForegroundColor Cyan ("CONTINUE: next target at QoS {0:N0} => {1:N0} IOPS from {2:N0} VMs, splitting prior measurments @ {3:N0} & {4:N0} IOPS/VM" -f ($qos,$tgtIOPS,$vms,($lower/$vms),($p2.IOPS/$vms))) + + # Similar to the adaptive case, if the split would logically close within the measurement window, this is now the last measurement. + # Indicate this so that regardless of result (say it does not scale) we complete after this next iteration. + if (IsWithin $tgtIOPS $p2.IOPS $qosWindow) + { + LogOutput -ForegroundColor Green ("INFILL COMPLETING: gap between {1:N0} and next target {2:N0} IOPS (QoS {3:N0}) is within {0:P1}" -f (($qosWindow/100),$p2.IOPS,$tgtIOPS,$qos)) + $last = $true + } + } + else + { + $qos = $minIOPS / 2 / $vms + LogOutput -ForegroundColor Cyan ("CONTINUE: next target at QoS {0:N0} => {1:N0} IOPS from {2:N0} VMs, halving minimum so far @ {3:N0} IOPS/VM" -f ($qos,($qos*$vms),$vms,($minIOPS/$vms))) + } + } + } + + # Coerce to integer for equality comparisons + $qos = [int] $qos + $tgtIOPS = $qos * $vms + + # Bottomed out QoS + if ($qos -eq 0) { break } + + # Final speedbrake - if we land on a prior QoS measurement, done with this phase of the loop. + if ($results |? QOSperVM -eq $qos) + { + LogOutput -ForegroundColor Cyan "STOP: result already captured at QoS $qos" + if ($sweepAdaptive) { + $sweepAdaptive = $false + continue + } + else + { + break + } + } + + # + # Iteration + # + + $o = DoIteration -QoS $qos -IsSearch -IsAdaptive:$sweepAdaptive + LogOutput -ForegroundColor Cyan ("RESULT: {0:N0} IOPS @ {1:P1} CPU => {2:N0} IOPS/VM from {3:N0} VMs v. target {4:N0} IOPS/VM" -f ($o.IOPS,($o.AverageCPU/100),($o.IOPS/$vms),$vms,$qos)) + + # + # If we have a surge result (workload went past window above throughput limit) there is a tool/systemic issue affecting + # results that will need external triage. Stop here. This is an error condition and not a normal cutoff. + # + + if ($o.CutoffType -eq [CutoffType]::Surge) + { + break + } + + } until ($last) + + # Log completion. Note that a thrown error will bypass and not log completion (try again?), and sweep terminating + # cases (e.g., in-window, unphysical QoS) that trigger before exhaustion are indeed (also) completion. + LogOutput -ForegroundColor Green "Sweep, complete" + LogToComplete + } + catch + { + if ($RunLabel.Length) + { + # If this is part of a labeled run, stash any iteration file content in error directory for analysis. + # Replace prior error content, if any. + $errd = (Join-Path $resultPath "error") + if (Test-Path $errd) { Remove-Item $errd -Recurse } + $null = mkdir $errd + $itf = @(GetIterationFiles $resultPath) + if ($itf.Count) + { + LogOutput "Iteration file content saved to $errd for triage" + + # Force continue - we don't want a stacked error trying to move content to lose + # the error which threw us into this condition. + $itf | Move-Item -Destination $errd -ErrorAction Continue + } + } + + throw + } + finally + { + # Clear QoS if used in this sweep + if ($UseStorageQos) + { + Set-FleetQos @clusterParam + } + } +} + +function Show-FleetCpuSweep +{ + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string] + $ResultFile, + + [Parameter()] + [int] + $SigFigs = 5 + ) + + function GetSigFigs( + [double]$Value, + [int]$SigFigs + ) + { + $log = [math]::Ceiling([math]::log10([math]::abs($value))) + $decimalpt = $sigfigs - $log + + # if all sigfigs are above the decimal point, round off to + # appropriate power of 10 + if ($decimalpt -lt 0) { + $pow = [math]::Abs($decimalpt) + $decimalpt = 0 + $value = [math]::Round($value/[math]::Pow(10,$pow))*[math]::pow(10,$pow) + } + + "{0:F$($decimalpt)}" -f $value + } + + function ShowReport + { + param( + [Parameter()] + [string] + $Header, + + [Parameter()] + [object[]] + $Data + ) + + Write-Host -ForegroundColor Cyan ("-"*20) + Write-Host "Variation: $Header" + + # Special case of a single measured run, no sweep + if ($Data.Count -eq 1) + { + Write-Host ("IOPS = {0} at AverageCPU {1:P2}" -f (GetSigFigs $Data.IOPS $SigFigs),($Data.AverageCPU/100)) + Write-Host "Single measurement of the workload." + return + } + + if ($Data.Count -eq 2) + { + Write-Host "Two measurements of the workload. TBD analysis statement." + return + } + + $fit = Get-FleetPolynomialFit -Order 2 -X $Data.AverageCPU -Y $Data.IOPS + + # sign of the linear/quad terms for the equation display + # the constant term falls out of the plain print + $sign = @() + if ( $fit.a[1] -ge 0) { $sign += '+ ' } else { $sign += '' } + if ( $fit.a[2] -ge 0) { $sign += '+ ' } else { $sign += '' } + + Write-Host ("IOPS = {0} {1}{2}(AverageCPU) {3}{4}(AverageCPU^2)" -f (GetSigFigs $fit.a[0] $SigFigs),$sign[0],(GetSigFigs $fit.a[1] $SigFigs),$sign[1],(GetSigFigs $fit.a[2] $SigFigs)) + Write-Host ("Number of measurements = {1}`nR^2 goodness of fit {0:P2}" -f $fit.r2,$Data.Count) + + # Use quadtratic form to estimate zero intercept (~idle cpu utilization) + # If the discriminant is < 0 something unreasonable happened (a complex solution) + # Note that the usual form will be an inverted parabola (a[2] < 0); provide the + # smallest positive root. + + $discriminant = [math]::pow($fit.a[1],2) - 4 * $fit.a[0] * $fit.a[2] + if ($discriminant -lt 0) + { + Write-Host -ForegroundColor Red "There is no solution for AverageCPU = 0; check results and system conditions for unexpected behavior" + } + else + { + $discriminant = [math]::Sqrt($discriminant) + $r0 = (-$fit.a[1] - $discriminant) / (2*$fit.a[2]) + $r1 = (-$fit.a[1] + $discriminant) / (2*$fit.a[2]) + $r = $null + if ($r0 -ge 0 -and $r0 -lt $r1) + { + $r = $r0 + } + if ($r1 -ge 0) + { + $r = $r1 + } + + if ($null -eq $r) + { + Write-Host -ForegroundColor Red ("Unexpected solutions for AverageCPU @ IOPS = 0 ({0:N2}, {1:N2}); check results and system conditions for unexpected behavior" -f $r0,$r1) + } + else + { + Write-Host ("Estimated background AverageCPU (CPU @ IOPS = 0): {0:P1}" -f ($r/100)) + } + } + } + + # Determine result grouping. We assume that the result file metadata columns end at QOS. + # To the left are arbitrary metadata columns provided by a runner (workload, alignment, etc.). + # Dynamically determine these metadata columns and group the results by them, producing a set of + # results to apply fits to. Some may be single values, not swept by QOS. + + $header = @((Get-Content $ResultFile -TotalCount 1) -split "`t" -replace '"','') + + if ($null -eq $header) + { + Write-Error "Could not read the table header from the result file - is this a CPU sweep result?" + return + } + + $splitIdx = $header.IndexOf('QOSperVM') + if ($splitIdx -eq -1) + { + Write-Error "Result file does not have a QOSperVM column - is this a CPU sweep result?" + return + } + + Write-Host @" +CPU Sweep Report + +The following models are quadratic fits to the measured results at the +given write ratios. + +Take care that these formulae are only used to reason about the region +where these values have a working relationship: + + AverageCPU > background and < 100% + +The second order term (AverageCPU^2) should generally be small negative +value indicating IOPS flattening as the system approaches saturation. + +Use R^2 (coefficient of determination) as a quality check for the fit. +Values close to 100% mean that the data is good fit. If R^2 is significantly +less than 100%, the fit is not good and a closer look at system behavior may +be required. +"@ + + if ($splitIdx -eq 0) + { + # single un-named data set + + ShowReport -Header '' -Data (Get-FleetResultLog $ResultFile) + return + } + + # Columns to the left of QOS minus internal metadata, forming the properties to group by. + $header = [collections.arraylist] $header[0..($splitIdx - 1)] + $header.Remove('RunType') + $header.Remove('CutoffType') + + # Process all groupings + Get-FleetResultLog $ResultFile | Group-Object -Property $header |% { + + $desc = $(foreach ($col in $header) + { + $col,$_.Group[0].$col -join ' = ' + }) -join "`n`t" + + ShowReport -Header $desc -Data $_.Group + } +} + +function SolveLU +{ + param( + [double[,]] + $m, + + [double[]] + $b + ) + + # m must be square + + if ($m.Rank -ne 2) + { + Write-Error "matrix m is not two dimensional: rank $($m.Rank)" + return + } + if ($m.GetLength(0) -ne $m.GetLength(1)) + { + Write-Error "matrix m is not square: $($m.GetLength(0)) x $($m.GetLength(1))" + return + } + if ($m.GetLength(0) -ne $b.GetLength(0)) + { + Write-Error "vector b is not of the same dimension as m: $($b.GetLength(0)) != $($m.GetLength(0))" + return + } + $n = $m.GetLength(0) + + # decompose matrix LU = L+U-I + $lu = [double[,]]::new($n, $n) + $sum = [double] 0 + for ($i = 0; $i -lt $n; ++$i) + { + for ($j = $i; $j -lt $n; ++$j) + { + $sum = [double] 0 + for ($k = 0; $k -lt $i; ++$k) + { + $sum += $lu[$i, $k] * $lu[$k, $j] + } + $lu[$i, $j] = $m[$i, $j] - $sum + } + + for ($j = $i + 1; $j -lt $n; ++$j) + { + $sum = [double] 0 + for ($k = 0; $k -lt $i; ++$k) + { + $sum += $lu[$j, $k] * $lu[$k, $i] + } + $lu[$j, $i] = (1 / $lu[$i, $i]) * ($m[$j, $i] - $sum) + } + } + + # find Ly = b + $y = [double[]]::new($n) + for ($i = 0; $i -lt $n; ++$i) + { + $sum = [double] 0 + for ($k = 0; $k -lt $i; ++$k) + { + $sum += $lu[$i, $k] * $y[$k] + } + $y[$i] = $b[$i] - $sum + } + + # find Ux = y + $x = [double[]]::new($n) + for ($i = $n - 1; $i -ge 0; --$i) + { + $sum = [double] 0 + for ($k = $i + 1; $k -lt $n; ++$k) + { + $sum += $lu[$i, $k] * $x[$k] + } + $x[$i] = (1 / $lu[$i, $i]) * ($y[$i] - $sum) + } + + ,$x +} + +function Get-FleetPolynomialFit +{ + [CmdletBinding()] + param( + [Parameter( + Mandatory = $true + )] + [int] + $Order, + + [Parameter( + Mandatory = $true + )] + [double[]] + $X, + + [Parameter( + Mandatory = $true + )] + [double[]] + $Y + ) + + # + # Return coefficients of least-squares fit for a polynomial of order Order + # over the input X/Y vectors. Return vector a is in ascending order: + # + # a0 + a1 x + a2 x^2 ... = y + # + # System of equations ma = b from polynominal residual function + # Due in part to discussion @ + # https://mathworld.wolfram.com/LeastSquaresFittingPolynomial.html + # https://en.wikipedia.org/wiki/LU_decomposition + # Additional reading: https://neutrium.net/mathematics/least-squares-fitting-of-a-polynomial/ + # + if ($Order -ge $X.Count) + { + Write-Error "Polynomial of order $Order requires at least $($Order + 1) values to fit: $($X.Count) provided" + return $null + } + if ($Y.Count -ne $X.Count) + { + Write-Error "Ranks of X ($($X.Count)) and Y ($($Y.Count)) input vectors not equal" + return $null + } + + # for all col + $m = [double[,]]::new($Order + 1, $Order + 1) + for ($j = 0; $j -le $Order; ++$j) + { + # for all rows below diagonal + for ($i = $j; $i -le $Order; ++$i) + { + # i, j + $m[$i, $j] = ($X |% { [math]::Pow($_, $i + $j) } | Measure-Object -Sum).Sum + # mirror over the diagonal: j, i = i, j + $m[$j, $i] = $m[$i, $j] + } + } + + $b = [double[]]::new($Order + 1) + for ($j = 0; $j -le $Order; ++$j) + { + for ($i = 0; $i -lt $X.Count; ++$i) + { + $b[$j] += [math]::Pow($X[$i], $j) * $Y[$i] + } + } + + $a = SolveLU $m $b + + # calculate residual r2 of the fit + $ssres = [double] 0 + $sstot = [double] 0 + $yMean = ($Y | Measure-Object -Average).Average + for ($i = 0; $i -lt $X.Count; ++$i) + { + $yFit = [double] 0 + for ($j = 0; $j -lt $a.Count; ++$j) + { + $yFit += $a[$j]*[math]::Pow($X[$i], $j) + } + + $ssres += [math]::pow($Y[$i] - $yFit, 2) + $sstot += [math]::pow($Y[$i] - $yMean, 2) + } + + # If sstot is zero this means all measurements are exactly the average value. + # In other words, it is a contstant and the non-constant coefficients are/should + # all be zero with a perfect fit. + if ($sstot -eq 0) + { + $r2 = 1 + } + else + { + $r2 = 1 - ($ssres/$sstot) + } + + + [PsCustomObject] @{ a = $a; r2 = $r2 } +} + +function Use-FleetPolynomialFit +{ + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [double[]] + $A, + + [Parameter(Mandatory = $true)] + [double] + $X + ) + + # Apply the polynomial function defined by the vector of coefficients A + # at the value X. + # + # Y = A0 + A1 X + A2 X^2 + ... + + $accX = [double] 1 + $accY = [double] 0 + foreach ($coeff in $A) + { + $accY += $accX * $coeff + $accX *= $X + } + + $accY +} + +function GetNextSplit +{ + param( + [Parameter(Mandatory = $true)] + [object[]] + $P, + + [Parameter(Mandatory = $true)] + [string] + $V, + + [Parameter(Mandatory = $true)] + [string] + $OrderBy + ) + + # This the adjacent pair of measured points in P with the largest gap in V when ordered by given field. + + $P = $P | Sort-Object -Property $OrderBy + + # Scan for the pair of values with the largest difference. + # Stop if lower result met cutoff conditions - we are willing to interpolate downward from + # a cutoff, but not above one. + $nextIdx = -1 + $nextDelta = 0 + for ($i = 0; $i -lt $P.Count - 1 -and $P[$i].CutoffType -eq [CutoffType]::No; ++$i) + { + $thisDelta = [math]::abs($P[$i + 1].$V - $P[$i].$V) + if ($thisDelta -gt $nextDelta) + { + $nextIdx = $i + $nextDelta = $thisDelta + } + } + + # Return the pair + if ($nextIdx -ge 0) + { + return $P[$nextIdx + 1],$P[$nextIdx] + } + else + { + return $P[0] + } +} + +function GetUpperAnchor +{ + param( + [Parameter(Mandatory = $true)] + [object[]] + $P, + + [Parameter(Mandatory = $true)] + [string] + $OrderBy + ) + + # Single/none + + if ($P.Count -eq 0) { return } + if ($P.Count -eq 1) { return $P[0] } + + # This the adjacent pair of points in P which, when ordered by the given field, the higher of + # which first hit a cutoff or is the highest value. + # + # Return is in descending order (high then low). Return may be none (if no points), one if the + # first point is already at cutoff OR single point present, or two otherwise. If caller says + # $h,$l = GetUpperAnchor then h/l will be $null as appropriate to the case. + + $P = $P | Sort-Object -Property $OrderBy + + $prior = $null + foreach ($pt in $P) + { + if ($pt.CutoffType -ne [CutoffType]::No) { return $pt,$prior } + $prior = $pt + } + + return $P[-1],$P[-2] +} + +function GetDistributedShift +{ + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [object[]] + $Group, + + [Parameter(Mandatory = $true)] + [uint32] + $N + ) + + foreach ($g in $Group) + { + if ($g.GetType().Name -ne "Object[]" -and $g.GetType().Name -ne "ArrayList") + { + Write-Error "Group input must be list of lists" + return + } + } + + if ($Group.Count -le 1) + { + Write-Error "Need two or more groups for distributed shift" + return + } + + $counts = @($Group[0].Count) + $e = $false + foreach ($g in $Group[1..($Group.Count - 1)]) + { + $counts += $g.Count + if ($g.Count -ne $counts[0]) + { + $e = $true + } + } + + if ($e) + { + Write-Error "Invalid groups for distributed shift - all must be same size ($($counts -join ' '))" + return + } + + if ($N -eq 0 -or $N -gt $Group[0].Count) + { + Write-Error "Invalid number of VMs/node to rotate - must be 0 < N < $($Group[0].Count)" + return + } + + # + # Distributed Shift: take the N VMs from the end of each ordered set per node and logically rotate + # the resulting groups by an increasing number of positions (1 <= rotation < #groups). + # + # 1: abcde + # 2: fghij + # 3: klmno + # + # => N = 1 + # 1 <= positions rotated + # 1: abcdo + # 2: fghie + # 3: klmnj + # + # => N = 2 + # 21 <= positions rotated + # 1: abcio + # 2: fghne + # 3: klmdj + # + # => N = 3 + # 121 <= positions rotated + # 1: abmio + # 2: fgcne + # 3: klhdj + # + + # + # Build arrays with the bottom elements which will not move. + # + + $arr = @() + + foreach ($g in $Group) + { + if ($N -lt $g.Count) + { + $arr += ,[collections.arraylist]@($g[0..($g.Count - $N - 1)]) + } + else + { + $arr += ,[collections.arraylist]@() + } + } + + # + # Now move across the groups. + # + # This is reversed since rotation amount starts at 1 at the end of the + # array and increases with lower indices. The shift starts in the middle + # and works to the end so we do not modify relative positions (and element + # at index 5 is always at index 5 of a rotation, regardless of how many + # VMs/node are rotated). + # + # Example: when distributing four groups at N = 6 we vary + # between rotating by 1, 2 and 3. This is what it looks + # like in index order + # + # 3 2 1 3 2 1 <- rotation in column + # 0 1 2 3 4 5 <- array index + # + + $rot = 1 + ($N-1)%($arr.Count-1) + for ($srcIdx = -$N; $srcIdx -lt 0; ++$srcIdx) + { + # + # Move across groups in the groups, rotating elements + # + + for($src = 0; $src -lt $arr.Count; ++$src) + { + $dst = ($src + $rot) % $arr.Count + $null = $arr[$dst].Add($Group[$src][$srcIdx]) + } + + # Roll rotation back to max at zero. Reversed since rotation + # is moving in the forward direction through the array. + --$rot + if (-not $rot) { $rot = $arr.Count - 1 } + } + + $arr +} + +function FilterObject +{ + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline = $true)] + $InputObject, + + [Parameter(Mandatory = $true)] + [hashtable] + $Filter + ) + + # + # Utility to filter pipeline objects with an AND equality test + # of all property/values provided in the filter. Note empty + # filter passes every element. + # + + process + { + $pass = $true + + foreach ($k in $Filter.Keys) + { + if ($_.$k -ne $Filter.$k) + { + $pass = $false + break + } + } + + if ($pass) { $_ } + } +} + +function SetCacheBehavior +{ + [CmdletBinding()] + param( + [Parameter()] + [string] + $Cluster = ".", + + [switch] + $PopulateOnFirstMiss + ) + + $property = @{ + Path = 'HKLM:\system\CurrentControlSet\Services\clusbflt\Parameters' + Name = 'CacheFlags' + } + + # Cache behavior modifications are set per node, not at the actual cluster level. This exposes + # a risk that if nodes are down or later added that the behaviors will vary across the cluster. + # This is not desirable, and is at the caller's risk to manage. + $nodes = @(Get-ClusterNode -Cluster $Cluster |? State -eq Up) + + foreach ($node in $nodes) + { + $s = New-PSSession -ComputerName $node + + try + { + $v = Invoke-Command -Session $s { Get-ItemProperty @using:property -ErrorAction SilentlyContinue } + if ($null -eq $v) + { + $currentValue = [uint32] 0 + } + else + { + $currentValue = [uint32] $v.CacheFlags + } + $newValue = $currentValue + + # + # Parameters + # + + if ($PSBoundParameters.ContainsKey('PopulateOnFirstMiss')) + { + $mask = [uint32] 0x80000 + + if ($PopulateOnFirstMiss) { $newValue = $newValue -bor $mask } + else { $newValue = $newValue -band -bnot $mask } + } + + # + # Apply + # + + # Do nothing? + if ($currentValue -eq $newValue) + { + continue + } + + Invoke-Command -session $s { + + # Get control object. If RefreshRegParams is not available, issue warning. + # This is only available with Server 2022+. + $dm = Get-WmiObject -namespace "root\wmi" ClusBfltDeviceMethods + if ($null -eq $dm -or -not (Get-Member -InputObject $dm -Name RefreshRegParams)) + { + Write-Warning "S2D Cache on $($env:COMPUTERNAME) does not support dynamic cache behavior refresh" + return $null + } + + Set-ItemProperty @using:property -Value ([uint32] $using:newValue) -Type ([Microsoft.Win32.RegistryValueKind]::DWord) + + # Trigger refresh - informational object returned which we can discard + $null = $dm.RefreshRegParams() + + # If value is now zero, remove it to keep the registry clean + if ($using:newValue -eq 0) + { + Remove-ItemProperty @using:property + } + } + } + finally + { + Remove-PSSession -Session $s + } + } +} + +function Get-Salt { + $salt = ([char]'a'..[char]'z' + [char]'A'..[char]'Z' + [char]'0'..[char]'9' | Get-Random -Count 4 |% { [char]$_ }) -join '' + Write-Verbose "Generated Salt: $salt" + return $salt; +} + +function Get-ArcConfig { + [CmdletBinding()] + param( + [Parameter()] + [string] + $Cluster = "." + ) + + $controlPath = Get-FleetPath -PathType Control $Cluster + $f = Join-Path $controlPath "arc.json" + if (-not (Test-Path $f)) { + Write-Verbose "arc.json does not exist. Current Arc mode: Disabled." + return $null + } + + $arcConfig = Get-Content -Path $f -Raw | ConvertFrom-Json + return $arcConfig +} + +function Set-ArcConfig { + [CmdletBinding()] + param( + [Parameter()] + [string]$ResourceGroup, + + [Parameter()] + [string]$AzureRegistrationUser, + + [Parameter()] + [string]$AzureRegistrationPassword, + + [Parameter()] + [string]$StoragePathCsv, + + [Parameter()] + [string]$StoragePathName = "arcVmFleetStoragePath", + + [Parameter()] + [string]$ImageName = "arcVmFleetImage", + + [Parameter()] + [bool] + $Enabled = $false, + + [Parameter()] + [switch] + $ResetSalt, + + [Parameter()] + [string] + $Cluster = "." + ) + + if($PSBoundParameters.Count -eq 0){ + Write-Error "Set atleast one property." + return; + } + if($PSBoundParameters.ContainsKey("ResetSalt")) { + $PSBoundParameters.Remove('ResetSalt') | Out-Null + } + $controlPath = Get-FleetPath -PathType Control $Cluster + $f = Join-Path $controlPath "arc.json" + $arcConfig = Get-ArcConfig $Cluster + if(!$arcConfig) { + $arcConfig = new-object PSCustomObject -Property $PSBoundParameters + } + else { + foreach($psbp in $PSBoundParameters.GetEnumerator()) + { + $arcConfig.($psbp.Key) = $psbp.Value + } + } + # Set default values for non-mandatory fields + if($arcConfig.psobject.properties.match('StoragePathName').Count -eq 0) { + $arcConfig | Add-Member -MemberType NoteProperty -Name 'StoragePathName' -Value $StoragePathName + } + if($arcConfig.psobject.properties.match('ImageName').Count -eq 0) { + $arcConfig | Add-Member -MemberType NoteProperty -Name 'ImageName' -Value $ImageName + } + if($arcConfig.psobject.properties.match('Enabled').Count -eq 0) { + $arcConfig | Add-Member -MemberType NoteProperty -Name 'Enabled' -Value $Enabled # disabled by default + } + if(!(Test-ArcConfig $arcConfig)){ + return; + } + $generateSalt = ($arcConfig.psobject.properties.match('Salt').Count -eq 0) -or $ResetSalt + if($generateSalt -or $PSBoundParameters.ContainsKey("ResourceGroup")) { + $retry = 0; + while ($retry -lt 3) { + if($generateSalt) { + $salt = Get-Salt + } + else { + $salt = $arcConfig.Salt + } + $nodes = @(Get-ClusterNode) + $isValidSalt = Invoke-CommonCommand $nodes[0] -InitBlock $CommonFunc -ScriptBlock { + InitializeAndGetArcHCIExtendedLoc $using:arcConfig.AzureRegistrationUser $using:arcConfig.AzureRegistrationPassword $using:arcConfig.ResourceGroup | Out-Null + $vmList = az stack-hci-vm list --resource-group $using:arcConfig.ResourceGroup | ConvertFrom-Json + $reg = "^vm-.{1,}-" + $using:salt + "-\d{3}$" + $vms = $vmList | Where-Object { $_.name -match $reg} + if($null -eq $vms) { return $true } + return $false; + } + + if(!$isValidSalt) { + $generateSalt = $true + $retry++ + } + else { + $arcConfig | Add-Member -MemberType NoteProperty -Name 'Salt' -Value $salt -Force + break + } + } + if($retry -eq 4) { + throw "Unable to create a unique salt with 3 retries. Pick another resource group or run Set-ArcConfig again." + } + } + + $arcConfig | ConvertTo-Json | Set-Content -Path $f -Force:$true + + Write-Host "Configuration updated in $f" +} + +function Test-ArcConfig { + [CmdletBinding()] + param ( + [Parameter(Mandatory=$true)] + [PSCustomObject] + $arcConfig + ) + $isValid = $true + $mandatoryFields = @("ResourceGroup", "AzureRegistrationUser", "AzureRegistrationPassword") + + foreach ($field in $mandatoryFields) { + if($arcConfig.psobject.properties.match($field).Count -eq 0) + { + $isValid = $false; + Write-Error "$field is required." + } + } + return $isValid; +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/WatchCPU.psm1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/WatchCPU.psm1 new file mode 100644 index 0000000..ddf6106 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/WatchCPU.psm1 @@ -0,0 +1,282 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +Set-StrictMode -Version 3.0 + +function Watch-FleetCPU +{ + [CmdletBinding()] + param( + [Parameter()] + [string] + $ComputerName = $env:COMPUTERNAME, + + [Parameter()] + [switch] + $Guest, + + [Parameter()] + [int] + $SampleInterval = 2 + ) + + function div-to-width( + [int] $div + ) + { + # 0 - 100 scale + # ex: 4 -> 100/4 = 25 buckets + 1 more for == 100 + 1+100/$div + } + + function center-pad( + [string] $s, + [int] $width + ) + { + if ($width -le $s.length) { + $s + } else { + (' ' * (($width - $s.length)/2) + $s) + } + } + + function get-legend( + [string] $label, + [int] $width, + [int] $div + ) + { + # now produce the scale, a digit at a time in vertical orientation + # at each multiple of 10% which aligns with a measurement bucket. + # the width is the width of the measured values + # + # 0 5 1 + # 0 0 + # 0 + + $lines = @() + $lines += '-' * $width + + foreach ($dig in 0..2) { + $o = foreach ($pos in 0..($width - 1)) { + + $val = $pos * $div + if ($val % 10 -eq 0) { + switch ($dig) { + 0 { if ($val -eq 100) { 1 } else { $val / 10 }} + 1 { if ($val -ne 0) { 0 } else { ' ' }} + 2 { if ($val -eq 100) { 0 } else { ' ' }} + } + } else { ' ' } + } + + $lines += $o -join '' + } + + # trailing comments (horizontal scale name) + $lines += center-pad $label $width + + $lines + } + + # minimum clip, the vertical height available for the cpu core bars + $minClip = 10 + + # these are the valid divisions, in units of percentage width. + # they must evenly divide 100% and either 10% or 20% for scale markings. + # determine which is the best fit based on window width. + + $div = 0 + $divs = 1,2,4,5 + foreach ($i in $divs) { + if ((div-to-width $i) -le [console]::WindowWidth) { + $div = $i + break + } + } + + # if nothing fit ... ask for minimum + # in practice this is not possible, but we check anyway + if ($div -eq 0) { + Write-Error "Window width must be at least $(div-to-width $divs[-1]) columns" + return + } + + $width = div-to-width $div + + # which processor counterset should we use? + # pi is only the root partition if hv is active; when hv is active: + # hvlp is the host physical processors + # hvvp is the guest virtual processors + # via ctrs, hv is active iff hvlp is present and has multiple instances + $cs = Get-Counter -ComputerName $ComputerName -ListSet 'Hyper-V Hypervisor Logical Processor' -ErrorAction SilentlyContinue + $hvactive = $null -ne $cs -and $cs.CounterSetType -eq [Diagnostics.PerformanceCounterCategoryType]::MultiInstance + + if ($Guest -and -not $hvactive) { + Write-Error "Hyper-V is not active on $ComputerName" + return + } + + if ($hvactive) { + if ($Guest) { + $cpuset = "\Hyper-V Hypervisor Virtual Processor(*)\% Guest Run Time" + $legend = get-legend "Percent Guest VCPU Utilization" $width $div + } else { + $cpuset = '\Hyper-V Hypervisor Logical Processor(*)\% Total Run Time' + $legend = get-legend "Percent Host LP Utilization" $width $div + } + } else { + $cpuset = '\Processor Information(*)\% Processor Time' + $legend = get-legend "Percent Processor Utilization" $width $div + } + + # processor performance counter (turbo/speedstep) + # this is used to normalize the total cpu utilization (can be > 100%) + $ppset = '\Processor Information(_Total)\% Processor Performance' + + # account for the constant legend in the window height + # use the remaining height for the vertical cpu core bars. + $clip = [console]::WindowHeight - ($legend.Count + 1) + + # insist on a minimum amount of space + if ($clip -lt $minClip) { + $minWindowHeight = $minClip + $legend.Count + 1 + Write-Error "Window height must be at least $minWindowHeight lines" + return + } + + # set window and buffer size simultaneously so we don't have extra scrollbars + Clear-Host + [console]::SetWindowSize($width, [console]::WindowHeight) + [console]::BufferWidth = [console]::WindowWidth + [console]::BufferHeight = [console]::WindowHeight + + # common params for Get-Counter + $gcParam = @{ + SampleInterval = $SampleInterval + Counter = $cpuset,$ppset + ErrorAction = 'SilentlyContinue' + } + + while ($true) { + + # reset measurements + # these are the vertical height of a cpu bar in each column, e.g. 2 = 2 cpus + $m = @([int] 0) * $width + + # avoid remoting for the local case + if ($ComputerName -eq $env:COMPUTERNAME) { + $ctrs = Get-Counter @gcParam + } else { + $ctrs = Get-Counter @gcParam -ComputerName $ComputerName + } + + # if more than one countersample was returned (ppset + something more), we have data + if ($null -ne $ctrs -and $ctrs.Countersamples.Count -gt 1) + { + # get all specific instances and count them into appropriate measurement bucket + $ctrs.Countersamples |% { + + # get scaling factor for total utility + if ($_.Path -like "*$ppset") { + $pperf = $_.CookedValue/100 + } + + # a cpu: count into appropriate measurement bucket + # (ignore total and/or and per-numa total) + elseif ($_.InstanceName -notlike '*_Total') { + $m[[math]::Floor($_.CookedValue/$div)] += 1 + } + + # get total + # + elseif ($_.InstanceName -eq '_Total') { + $total = $_.CookedValue + } + } + + # now produce the bar area as a series of lines + # work down the veritical altitude of each strip, starting at the clip/top + $altitude = $clip + $lines = do { + $($m |% { + + # top line - if we are potentially clipped, handle + if ($altitude -eq $clip) { + + # clipped? + if ($_ -gt $altitude) { 'x' } + # unclipped but at clip? + elseif ($_ -eq $altitude) { '*' } + # nothing, bar less than altitude + else { ' ' } + + } else { + + # below top line + # >=, output bar + if ($_ -ge $altitude) { '*' } + # <, nothing + else { ' ' } + } + }) -join '' + } while (--$altitude) + + $totalStr = "{0:0.0}%" -f $total + $normalStr = "{0:0.0}%" -f ($total*$pperf) + + # move the cursor to indicate average utilization + # column number is zero based, width is 1-based + $cpos = [math]::Floor(($width - 1)*$total/100) + } + else + { + # Center no data message vertically and horizontally in the frame + + $vpre = [math]::Floor($clip/2) - 1 + $vpost = [math]::Floor($clip/2) + + $lines = @('') * $vpre + $lines += center-pad "No Data Available" $width + $lines += @('') * $vpost + + $totalStr = $normalStr = "---" + + # zero cursor + $cpos = 0 + } + + Clear-Host + Write-Host -NoNewline ($lines + $legend -join "`n") + Write-Host -NoNewLine ("`n" + (center-pad "$ComputerName Total: $totalStr Normalized: $normalStr" $width)) + + # move the cursor to indicate average utilization + # column number is zero based, width is 1-based + [console]::SetCursorPosition($cpos,[console]::CursorTop-$legend.Count) + } +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/WatchCluster.psm1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/WatchCluster.psm1 new file mode 100644 index 0000000..c7d0908 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet/WatchCluster.psm1 @@ -0,0 +1,552 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +Set-StrictMode -Version 3.0 + +# display name +# ctr name +# display order +# format string +# scalar multiplier + +class CounterColumn { + + [string] $displayname + [string] $setname + [string[]] $ctrname + [int] $width + [string] $fmt + [decimal] $multiplier + [ValidateSet("Average","AverageAggregate","Sum")][string] $aggregate + [boolean] $divider + + CounterColumn( + [string] $displayname, + [string] $setname, + [string[]] $ctrname, + [int] $width, + [string] $fmt, + [decimal] $multiplier, + [string] $aggregate, + [boolean] $divider + ) + { + $this.displayname = $displayname + $this.setname = $setname + $this.ctrname = $ctrname + $this.width = $width + $this.fmt = $fmt + $this.multiplier = $multiplier + $this.aggregate = $aggregate + $this.divider = $divider + + if ($this.width -lt $this.displayname.length + 1) { + $this.width = $this.displayname.length + 1 + } + } +} + +class CounterColumnSet { + + [CounterColumn[]] $columns + [string[]] $counters + [string] $topfmt + [string] $linfmt + [string] $name + + [string] $totalline + [string[]] $nodelines + + CounterColumnSet($name) + { + $this.columns = $null + $this.name = $name + } + + [void] Add([CounterColumn] $c) + { + $this.columns += $c + } + + [void] Seal() + { + # assemble the top-line fmt and per-line fmt strings + # top-line is just strings/width + # per-line adds the (likely numeric) format specifier + $n = 1 + $this.topfmt = $this.linfmt = "{0,-16}" + foreach ($col in $this.columns) { + $str = '' + if ($col.divider) { + $str += '| ' + } + $str += "{$n,-$($col.width)" + $this.topfmt += "$str}" + $this.linfmt += "$($str):$($col.fmt)}" + + $n += 1 + } + + # assemble the list of counter instances that will be needed + # note that some may be internally aggregated (i.e., total = read + write) + # in cases where a counterset does not provide an explicit total + $this.counters = ($this.columns |% { + $s = $_.setname + $_.ctrname |% { "\$($s)(_Total)\$($_)" } + } | group -NoElement).Name + } + + [void] DisplayPre( + [hashtable] $samples, + [hashtable] $psamples + ) + { + # aggregate each column across all live sampled nodes + $this.totalline = $this.linfmt -f $( + "Total" + foreach ($col in $this.columns) { + + # average aggreate doesn't work across nodes if the base is not consistent + # for instance: cannot average latency safely, but can average cpu utilization + if ($col.aggregate -ne 'Average' -or $col.aggregate -eq 'AverageAggregate') { + $(foreach ($node in $psamples.keys) { + get-samples $psamples $node $col + }) | get-aggregate $col + } else { + $null + } + } + ) + + # individual nodes + # flags downed/non-responsive nodes by noting which are not + # present in the processed samples + $this.nodelines = foreach ($node in $samples.keys | sort) { + if ($psamples.ContainsKey($node)) { + $this.linfmt -f $( + $node + foreach ($col in $this.columns) { + $s = get-samples $psamples $node $col + if ($null -ne $s) { + $a = $s | get-aggregate $col + $a + } else { + "-" + } + } + ) + } else { + $this.topfmt -f $( + $node + 0..($this.columns.count - 1) |% { "-" } + ) + } + } + } + + [void] Display() + { + write-host ($this.topfmt -f (,$this.name + $this.columns.displayname)) + write-host -fore green $this.totalline + $this.nodelines |% { write-host $_ } + } +} + +function Watch-FleetCluster +{ + + [CmdletBinding()] + param( + [Parameter()] + [string] + $Cluster = ".", + + [Parameter()] + [int] + $SampleInterval = 2, + + [Parameter()] + [ValidateSet("CSV FS","SSB Cache","SBL","SBL Local","SBL Remote","SBL*","S2D BW","Hyper-V LCPU","SMB SRV","SMB Transport","*")] + [string[]] $Sets = "CSV FS", + + [Parameter()] + [string] + $LogFile + ) + + if ($PSBoundParameters.ContainsKey('LogFile')) + { + $script:log = $LogFile + Remove-Item -Force $log -ErrorAction SilentlyContinue + } + + function WriteLog( + [Parameter(ValueFromPipeline = $true)] + [string[]] + $String + ) + { + PROCESS { + if ($null -ne $script:log) { + "$(get-date) $String" | Out-File -Append -FilePath $script:log -Width 9999 -Encoding ascii + } + } + } + + function get-aggregate( + [CounterColumn] $col + ) + { + BEGIN { + $n = 0 + $v = 0 + } + PROCESS { + $n += 1 + $v += $_ + } + END { + if ($n -gt 0) { + switch -wildcard ($col.aggregate) { + 'Sum' { + #write-host $col.displayname $col.multipler $v + $col.multiplier * $v + } + 'Average*' { + #write-host $col.displayname $col.multiplier $v $n + $col.multiplier * $v / $n + } + } + } else { + $null + } + } + } + + # get samples out of per-node hash of ctr hashes + function get-samples( + [hashtable] $h, + [string] $node, + [CounterColumn] $col + ) + { + foreach ($i in $col.ctrname) { + + $k = "$($col.setname)+$($i)" + + if ($h.ContainsKey($node)) { + if ($h[$node].ContainsKey($k)) { + $h[$node][$k] + } else { + WriteLog "missing $node[$k] : $($h[$node].Keys.Count) total keys" + } + } else { + WriteLog "missing $node" + } + } + } + + $allctrs = @() + + ### + $c = [CounterColumnSet]::new("CSV FS") + $c.Add([CounterColumn]::new("IOPS", "Cluster CSVFS", @("Reads/sec","Writes/sec"), 12, '#,#', 1, 'Sum', $false)) + $c.Add([CounterColumn]::new("Reads", "Cluster CSVFS", @("Reads/sec"), 12, '#,#', 1, 'Sum', $false)) + $c.Add([CounterColumn]::new("Writes", "Cluster CSVFS", @("Writes/sec"), 12, '#,#', 1, 'Sum', $false)) + + $c.Add([CounterColumn]::new("BW (MB/s)", "Cluster CSVFS", @("Read Bytes/sec","Write Bytes/sec"), 13, '#,#', 0.000001, 'Sum', $true)) + $c.Add([CounterColumn]::new("Read", "Cluster CSVFS", @("Read Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + $c.Add([CounterColumn]::new("Write", "Cluster CSVFS", @("Write Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + + $c.Add([CounterColumn]::new("Read Lat (ms)", "Cluster CSVFS", @("Avg. sec/Read"), 15, '0.000', 1000, 'Average', $true)) + $c.Add([CounterColumn]::new("Write", "Cluster CSVFS", @("Avg. sec/Write"), 8, '0.000', 1000, 'Average', $false)) + + $c.Add([CounterColumn]::new("Read QAvg", "Cluster CSVFS", @("Avg. Read Queue Length"), 11, '0.000', 1, 'Average', $true)) + $c.Add([CounterColumn]::new("Write", "Cluster CSVFS", @("Avg. Write Queue Length"), 8, '0.000', 1, 'Average', $false)) + + $c.Seal() + $allctrs += $c + + ### + $c = [CounterColumnSet]::new("SSB Cache") + + $c.Add([CounterColumn]::new("Hit/Sec", "Cluster Storage Hybrid Disks", @("Cache Hit Reads/Sec"), 12, '#,#', 1, 'Sum', $false)) + $c.Add([CounterColumn]::new("Miss/Sec", "Cluster Storage Hybrid Disks", @("Cache Miss Reads/Sec"), 12, '#,#', 1, 'Sum', $false)) + $c.Add([CounterColumn]::new("Remap/Sec" ,"Cluster Storage Cache Stores", @("Page ReMap/sec"), 12, '#,#', 1, 'Sum', $false)) + + $c.Add([CounterColumn]::new("Cache (MB/s)", "Cluster Storage Hybrid Disks", @("Cache Populate Bytes/sec","Cache Write Bytes/sec"), 13, '#,#', 0.000001, 'Sum', $true)) + $c.Add([CounterColumn]::new("RdPop", "Cluster Storage Hybrid Disks", @("Cache Populate Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + $c.Add([CounterColumn]::new("WrPop", "Cluster Storage Hybrid Disks", @("Cache Write Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + + $c.Add([CounterColumn]::new("Destage (MB/s)", "Cluster Storage Cache Stores", @("Destage Bytes/sec"), 15, '#,#', 0.000001, 'Sum', $true)) + $c.Add([CounterColumn]::new("Update", "Cluster Storage Cache Stores", @("Update Bytes/sec"), 7, '#,#', 0.000001, 'Sum', $false)) + + $c.Add([CounterColumn]::new("Total (Pgs)", "Cluster Storage Cache Stores", @("Cache Pages"), 11, '0.00E+0', 1, 'Sum', $true)) + $c.Add([CounterColumn]::new("Standby", "Cluster Storage Cache Stores", @("Cache Pages StandBy"), 9, '0.00E+0', 1, 'Sum', $false)) + $c.Add([CounterColumn]::new("L0", "Cluster Storage Cache Stores", @("Cache Pages StandBy L0"), 9, '0.00E+0', 1, 'Sum', $false)) + $c.Add([CounterColumn]::new("L1", "Cluster Storage Cache Stores", @("Cache Pages StandBy L1"), 9, '0.00E+0', 1, 'Sum', $false)) + $c.Add([CounterColumn]::new("L2", "Cluster Storage Cache Stores", @("Cache Pages StandBy L2"), 9, '0.00E+0', 1, 'Sum', $false)) + $c.Add([CounterColumn]::new("Dirty", "Cluster Storage Cache Stores", @("Cache Pages Dirty"), 9, '0.00E+0', 1, 'Sum', $false)) + + $c.Seal() + $allctrs += $c + + ### + + foreach ($subset in '','Local','Remote') { + $name = 'SBL' + $prefix = '' + if ($subset.Length) { + $name += " $subset" + $prefix = "$($subset): " + } + + $c = [CounterColumnSet]::new($name) + + $c.Add([CounterColumn]::new("IOPS", "Cluster Disk Counters", @(($prefix + "Read/sec"),($prefix + "Writes/sec")), 12, '#,#', 1, 'Sum', $false)) + $c.Add([CounterColumn]::new("Reads", "Cluster Disk Counters", @($prefix + "Read/sec"), 12, '#,#', 1, 'Sum', $false)) + $c.Add([CounterColumn]::new("Writes", "Cluster Disk Counters", @($prefix + "Writes/sec"), 12, '#,#', 1, 'Sum', $false)) + + $c.Add([CounterColumn]::new("BW (MB/s)", "Cluster Disk Counters", @(($prefix + "Read - Bytes/sec"),($prefix + "Write - Bytes/sec")), 13, '#,#', 0.000001, 'Sum', $true)) + $c.Add([CounterColumn]::new("Read", "Cluster Disk Counters", @($prefix + "Read - Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + $c.Add([CounterColumn]::new("Write", "Cluster Disk Counters", @($prefix + "Write - Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + + $c.Add([CounterColumn]::new("Read Lat (ms)", "Cluster Disk Counters", @($prefix + "Read Latency"), 15, '0.000', 1000, 'Average', $true)) + $c.Add([CounterColumn]::new("Write", "Cluster Disk Counters", @($prefix + "Write Latency"), 8, '0.000', 1000, 'Average', $false)) + + $c.Add([CounterColumn]::new("Read QAvg", "Cluster Disk Counters", @($prefix + "Read Avg. Queue Length"), 11, '0.000', 1, 'Average', $true)) + $c.Add([CounterColumn]::new("Write", "Cluster Disk Counters", @($prefix + "Write Avg. Queue Length"), 8, '0.000', 1, 'Average', $false)) + + $c.Seal() + $allctrs += $c + } + + ### + $c = [CounterColumnSet]::new("SMB SRV") + + $c.Add([CounterColumn]::new("IOPS", "SMB Server Shares", @("Data Requests/sec"), 12, '#,#', 1, 'Sum', $false)) + $c.Add([CounterColumn]::new("Reads", "SMB Server Shares", @("Read Requests/sec"), 12, '#,#', 1, 'Sum', $false)) + $c.Add([CounterColumn]::new("Writes", "SMB Server Shares", @("Write Requests/sec"), 12, '#,#', 1, 'Sum', $false)) + + $c.Add([CounterColumn]::new("Data BW (MB/s)", "SMB Server Shares", @("Data Bytes/sec"), 13, '#,#', 0.000001, 'Sum', $true)) + $c.Add([CounterColumn]::new("Read", "SMB Server Shares", @("Read Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + $c.Add([CounterColumn]::new("Write", "SMB Server Shares", @("Write Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + + $c.Add([CounterColumn]::new("Total BW (MB/s)", "SMB Server Shares", @("Transferred Bytes/sec"), 13, '#,#', 0.000001, 'Sum', $true)) + $c.Add([CounterColumn]::new("Rcv", "SMB Server Shares", @("Received Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + $c.Add([CounterColumn]::new("Snd", "SMB Server Shares", @("Sent Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + + $c.Seal() + $allctrs += $c + + ## + $c = [CounterColumnSet]::new("S2D BW") + + $c.Add([CounterColumn]::new("CSV (MB/s)", "Cluster CSVFS", @("Read Bytes/sec","Write Bytes/sec"), 10, '#,#', 0.000001, 'Sum', $false)) + $c.Add([CounterColumn]::new("Read", "Cluster CSVFS", @("Read Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + $c.Add([CounterColumn]::new("Write", "Cluster CSVFS", @("Write Bytes/sec"), 8 ,'#,#', 0.000001, 'Sum', $false)) + + $c.Add([CounterColumn]::new("SBL (MB/s)", "Cluster Disk Counters", @("Read - Bytes/sec","Write - Bytes/sec"), 10, '#,#', 0.000001, 'Sum', $true)) + $c.Add([CounterColumn]::new("Read", "Cluster Disk Counters", @("Read - Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + $c.Add([CounterColumn]::new("Write", "Cluster Disk Counters", @("Write - Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + + $c.Add([CounterColumn]::new("Cache (MB/s)", "Cluster Storage Hybrid Disks", @("Cache Hit Read Bytes/sec","Cache Write Bytes/sec"), 12, '#,#', 0.000001, 'Sum', $true)) + $c.Add([CounterColumn]::new("Read", "Cluster Storage Hybrid Disks", @("Cache Hit Read Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + $c.Add([CounterColumn]::new("Write", "Cluster Storage Hybrid Disks", @("Cache Write Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + + $c.Add([CounterColumn]::new("Disk (MB/s)", "Cluster Storage Hybrid Disks", @("Disk Read Bytes/sec","Disk Write Bytes/sec"), 11, '#,#', 0.000001, 'Sum', $true)) + $c.Add([CounterColumn]::new("Read", "Cluster Storage Hybrid Disks", @("Disk Read Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + $c.Add([CounterColumn]::new("Write", "Cluster Storage Hybrid Disks", @("Disk Write Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + + $c.Seal() + $allctrs += $c + + ## + $c = [CounterColumnSet]::new("Hyper-V LCPU") + $c.Add([CounterColumn]::new("Logical Total%", "Hyper-V Hypervisor Logical Processor", @("% Total Run Time"), 8, "0.00", 1, 'AverageAggregate', $false)) + $c.Add([CounterColumn]::new("Guest%", "Hyper-V Hypervisor Logical Processor", @("% Guest Run Time"), 8, "0.00", 1, 'AverageAggregate', $false)) + $c.Add([CounterColumn]::new("Hypervisor%", "Hyper-V Hypervisor Logical Processor", @("% Hypervisor Run Time"), 13, "0.00", 1, 'AverageAggregate', $false)) + + $c.Add([CounterColumn]::new("Root Total%", "Hyper-V Hypervisor Root Virtual Processor", @("% Total Run Time"), 12, "0.00", 1, 'AverageAggregate', $true)) + $c.Add([CounterColumn]::new("Guest%", "Hyper-V Hypervisor Root Virtual Processor", @("% Guest Run Time"), 8, "0.00", 1, 'AverageAggregate', $false)) + $c.Add([CounterColumn]::new("Hypervisor%", "Hyper-V Hypervisor Root Virtual Processor", @("% Hypervisor Run Time"), 12, "0.00", 1, 'AverageAggregate', $false)) + $c.Add([CounterColumn]::new("Remote%", "Hyper-V Hypervisor Root Virtual Processor", @("% Remote Run Time"), 7, "0.00", 1, 'AverageAggregate', $false)) + + $c.Seal() + $allctrs += $c + + ## + $c = [CounterColumnSet]::new("SMB Transport") + $c.add([CounterColumn]::new("Read IOPS", "SMB Client Shares", @("Read Requests/sec"), 11, "#,#", 1, 'Sum', $false)) + $c.add([CounterColumn]::new("Write", "SMB Client Shares", @("Write Requests/sec"), 8, "#,#", 1, 'Sum', $false)) + + $c.add([CounterColumn]::new("RDMA Read", "SMB Client Shares", @("Read Requests transmitted via SMB Direct/sec"), 11, "#,#", 1, 'Sum', $true)) + $c.add([CounterColumn]::new("Write", "SMB Client Shares", @("Write Requests transmitted via SMB Direct/sec"), 8, "#,#", 1, 'Sum', $false)) + + $c.Seal() + $allctrs += $c + + ## + if ($Sets.Count -eq 1 -and $Sets[0] -eq '*') { + $ctrs = $allctrs + } else { + $ctrs = $Sets |% { + $s = $_ + $allctrs |? { $_.name -like $s } # allows the SBL* wildcard + } + } + + function start-sample( + [CounterColumnSet[]] $ctrs, + [int] $SampleInterval + ) + { + # clear any previous job instance + Get-Job -Name watch-cluster -ErrorAction SilentlyContinue | Stop-Job + Get-Job -Name watch-cluster -ErrorAction SilentlyContinue | Remove-Job + + # flatten list of counters and uniquify for the total counter set + # some display counter sets may repeat specific values (which is fine) + $counters = ($ctrs.counters |% { $_ |% { $_ }} | group -NoElement).Name + + icm -AsJob -JobName watch-cluster (Get-ClusterNode -Cluster $Cluster) { + + # extract countersamples, the object does not survive transfer between powershell sessions + # extract as a list, not as the individual counters + get-counter -Continuous -SampleInterval $using:SampleInterval $using:ctrs.counters |% { + ,$_.countersamples + } + } + } + + # start the first sample job and allow frame draw the first time through + $j = start-sample $ctrs $SampleInterval + $downtime = $null + $skipone = $false + $loops = 0 + $restart = $false + + # hash of most recent samples/node + $samples = @{} + Get-ClusterNode -Cluster $Cluster |% { $samples[$_.Name] = $null } + + while ($true) { + + if (-not $restart) { + Start-Sleep -Seconds $SampleInterval + + # sleep again if needed to prime the sample pipeline; + # there are no samples if we just restarted the sampling jobs + if ($skipone) { + $skipone = $false + continue + } + + # receive updates into the per-node hash + foreach ($child in $j.ChildJobs) { + $samples[$child.Location] = $child | receive-job -ErrorAction SilentlyContinue + } + + # null out downed nodes and remember first time we saw one drop out + $down = 0 + $j.ChildJobs |? State -ne Running |% { + $samples[$_.Location] = $null + $down += 1 + } + if ($down -and $null -eq $downtime) { + $downtime = get-date + } + + # if everything is down, we will attempt restart + if ($down -eq $j.ChildJobs.Count) { + $restart = $true + break + } + } + + # if explicit restart is required, or it has been 30 seconds with a downed node, restart the jobs to retry + if ($restart -or ($null -ne $downtime -and ((get-date)-$downtime).totalseconds -gt 30)) { + $j | stop-job + $j | remove-job + $j = start-sample $ctrs $SampleInterval + + # force gc to clear out accumulated job state quickly + [system.gc]::Collect() + + $downtime = $null + $skipone = $true + $restart = $false + continue + } + + # now process samples into per-node hashes of set/ctr containing lists of the + # cooked values acrosss the (possible) multiple instances + $psamples = @{} + foreach ($node in $samples.keys) { + + if ($samples[$node]) { + + $nsamples = @{} + + # flatten samples - if we are lagging, we'll have a list + # of consecutive (increasing by timestamp) samples + # we could try to be more efficient by dumping all but the + # final sample, but later ... + $samples[$node] |% { $_ } |% { + + ($setinst,$ctr) = $($_.path -split '\\')[3..4] + $set = ($setinst -split '\(')[0] + + $k = "$set+$ctr" + $nsamples[$k] = $_.cookedvalue + } + + $psamples[$node] = $nsamples + } + } + + # post-process the samples into the counterset, then clear and dump + $ctrs.DisplayPre($samples, $psamples) + Clear-Host + $drawsep = $false + $ctrs |% { + if ($drawsep) { + write-host -fore Green $('-'*20) + } + $drawsep = $true + $_.Display() + + } + + # restart the jobs every so many loops to prevent resource growth + $loops += 1 + if ($loops -gt 100) { + $loops = 0 + $restart = $true + } + } +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/README.md b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/README.md new file mode 100644 index 0000000..3b3fe94 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/README.md @@ -0,0 +1,47 @@ +## VM Fleet ## + +These are the historical release comments for VM Fleet 1.0. + +VM Fleet 0.9 10/2017 (minor) + +* watch-cpu: now provides total normalized cpu utility (accounting for turbo/speedstep) +* sweep-cputarget: now provides average CSV FS read/write latency in the csv + +VM Fleet 0.8 6/2017 + +* get-cluspc: add SMB Client/Server and SMB Direct (not defaulted in Storage group yet) +* test-clusterhealth: flush output pipeline for Debug-StorageSubsystem output +* watch-cluster: restart immediately if all child jobs are no longer running +* watch-cpu: new, visualizer for CPU core utilization distributions + +VM Fleet 0.7 3/2017 + +* create/destroy-vmfleet & update-csv: don't rely on the csv name containing the friendlyname of the vd +* create-vmfleet: err if basevhd inaccessible +* create-vmfleet: simplify call-throughs using $using: syntax +* create-vmfleet: change vhdx layout to match scvmm behavior of seperate directory per VM (important for ReFS MRV) +* create-vmfleet: use A1 VM size by default (1VCPU 1.75GiB RAM) +* start-vmfleet: try starting "failed" vms, usually works +* set-vmfleet: add support for -SizeSpec for A/D/D2v1 & v2 size specification, for ease of reconfig +* stop-vmfleet: pass in full namelist to allow best-case internal parallelization of shutdown +* sweep-cputarget: use %Processor Performance to rescale utilization and account for Turbo effects +* test-clusterhealth: support cleaning out dumps/triage material to simplify ongoing monitoring (assume they're already gathered/etc.) +* test-clusterhealth: additional triage output for storport unresponsive device events +* test-clusterhealth: additional triage comments on SMB client connectivity events +* test-clusterhealth: new test for Mellanox CX3/CX4 error counters that diagnose fabric issues (bad cable/transceiver/roce specifics/etc.) +* get-log: new triage log gatherer for all hv/clustering/smb event channels +* get-cluspc: new cross-cluster performance counter gatherer +* remove run-<>.ps1 scripts that were replaced with run-demo-<>.ps1 +* check-outlier: EXPERIMENTAL way to ferret out outlier devices in the cluster, using average sampled latency + +VM Fleet 0.6 7/18/2016 + +* CPU Target Sweep: a sweep script using StorageQoS and a linear CPU/IOPS model to build an empirical sweep of IOPS as a function of CPU, initially for the three classic small IOPS mixes (100r, 90:10 and 70:30 4K). Includes an analysis script which provides the linear model for each off of the results. +* Update sweep mechanics which allow generalized specification of DISKSPD sweep parameters and host performance counter capture. +* install-vmfleet to automate placement after CSV/VD structure is in place (add path, create dirs, copyin, pause) +* add non-linearity detection to analyze-cputarget +* get-linfit is now a utility script (produces objects describing fits) +* all flag files (pause/go/done) pushed down to control\flag directory +* demo scripting works again and autofills vm/node counts +* watch-cluster handles downed/recovered nodes gracefully +* update-csv now handles node names which are logical prefixes of another (node1, node10) \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/analyze-cputarget.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/analyze-cputarget.ps1 new file mode 100644 index 0000000..5b4526a --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/analyze-cputarget.ps1 @@ -0,0 +1,79 @@ +param( + [string] $csvfile = $(throw "please provide the path to a cputarget sweep result file"), + [switch] $zerointercept = $false, + [int] $sigfigs = 5 + ) + +function get-sigfigs( + [decimal]$value, + [int]$sigfigs + ) +{ + $log = [math]::Ceiling([math]::log10([math]::abs($value))) + $decimalpt = $sigfigs - $log + + # if all sigfigs are above the decimal point, round off to + # appropriate power of 10 + if ($decimalpt -lt 0) { + $pow = [math]::Abs($decimalpt) + $decimalpt = 0 + $value = [math]::Round($value/[math]::Pow(10,$pow))*[math]::pow(10,$pow) + } + + "{0:F$($decimalpt)}" -f $value +} + +write-host -ForegroundColor Green CPU Target Sweep Report`n +write-host -ForegroundColor Green The following equations and coefficients are the linear +write-host -ForegroundColor Green fit to the measured results at the given write ratios. +write-host -ForegroundColor Cyan ("-"*20) + +write-host -ForegroundColor Yellow NOTE: take care that these formula are only used to reason about +write-host -ForegroundColor Yellow " " the region where these values are in a linear relationship. +write-host -ForegroundColor Yellow " In particular, at high AVCPU the system may be saturated." +write-host -ForegroundColor Yellow "Use R^2 (coefficient of determination) as a quality check for the fit." +write-host -ForegroundColor Yellow "Values close to 100% mean that the data is indeed linear. If R2 is" +write-host -ForegroundColor Yellow "significantly less than 100%, a closer look at system behavior may" +write-host -ForegroundColor Yellow "be required." + +if ($zerointercept) { + write-host -ForegroundColor Red NOTE: forcing to a "(AVCPU=0,IOPS=0)" intercept may introduce error +} else { + write-host -ForegroundColor Red "NOTE: with a non-zero constant coefficient, care should be used at`nlow AVCPU that the result is meaningful" +} + +# do the check fit of QoS to IOPS +# this will let us check for non-CPU limited saturation (poor r2 is a giveaway) +# we can have an excellent CPU->IOPS fit but not actually have been able to stress in much CPU +# and have lots of repeated measurements as we tried to step up QoS +$h = @{} +get-linfit -csvfile $csvfile -xcol QOS -ycol IOPS -idxcol WriteRatio -zerointercept:$zerointercept |% { + $h[$_.Key] = $_ +} + +# now fit IOPS to Average CPU +get-linfit -csvfile $csvfile -xcol AVCPU -ycol IOPS -idxcol WriteRatio -zerointercept:$zerointercept | sort -Property Key |% { + + write-host -ForegroundColor Cyan ("-"*20) + write-host $_.Key + + if ($zerointercept) { + write-host ("{0} = {1}({2})" -f $_.Y,$(get-sigfigs $_.B $sigfigs),$_.X) + } else { + + if ($_.A -ge 0) { + $sign = "+" + } else { + $sign = "" + } + + write-host ("{0} = {1}({2}){4}{3}" -f $_.Y,$(get-sigfigs $_.B $sigfigs),$_.X,$(get-sigfigs $_.A $sigfigs),$sign) + } + + write-host ("N = {1}`nR^2 goodness of fit {0:P2}" -f $_.R2,$_.N) + if ($h[$_.Key].R2 -le 0.5) { + write-host -ForegroundColor Yellow "WARNING: for $($_.Key) it does not appear that IOPS moved in" + write-host -ForegroundColor Yellow "`trelation to attempts to raise the QoS limit. Check if the" + write-host -ForegroundColor Yellow "`tsystem is storage limited for this mix." + } +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/check-outlier.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/check-outlier.ps1 new file mode 100644 index 0000000..ffc621c --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/check-outlier.ps1 @@ -0,0 +1,201 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +param ( + [int] $interval = 10 + ) + +class RunningStat { + + # This is an implementation of an online (add one value at a time) method to calculate + # up to the fourth central moment (mean, variance, skewness and kurtosis) of a series + # of decimal values. + # + # Implementation is due to https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance + # which is in turn due http://people.xiph.org/~tterribe/notes/homs.html by Timothy Terriberry, + # also cited by Pebay: http://prod.sandia.gov/techlib/access-control.cgi/2008/086212.pdf + # + # Note: skewness appears to match Excel calculations. Kurtosis does NOT at this time, and + # should be used with caution. + + [decimal] $n; + [decimal] $M1; + [decimal] $M2; + [decimal] $M3; + [decimal] $M4; + [decimal] $Min; + [decimal] $Max; + + RunningStat() + { + $this.M1 = 0 + $this.M2 = 0 + $this.M3 = 0 + $this.M4 = 0 + $this.Min = 0 + $this.Max = 0 + } + + [void] Clear() + { + $this.M1 = 0 + $this.M2 = 0 + $this.M3 = 0 + $this.M4 = 0 + $this.Min = 0 + $this.Max = 0 + } + + [void] Add([decimal] $v) + { + $n1 = $this.n + $this.n += 1 + $delta = $v - $this.M1 + $deltan = $delta / $this.n + $deltan2 = $deltan * $deltan + $term1 = $delta * $deltan * $n1 + $this.M1 += $deltan + $this.M4 += $term1 * $deltan2 * (($this.n * $this.n) - (3 * $this.n) + 3) + (6 * $deltan2 * $this.M2) - (4 * $deltan * $this.M3) + $this.M3 += $term1 * $deltan * ($this.n - 2) - (3 * $deltan * $this.M2) + $this.M2 += $term1 + + if ($n1 -eq 0 -or $v -gt $this.Max) { $this.Max = $v } + if ($n1 -eq 0 -or $v -lt $this.Min) { $this.Min = $v } + } + + [decimal] Min() + { + return $this.Min + } + + [decimal] Max() + { + return $this.Max + } + + [decimal] Mean() + { + return $this.M1 + } + + [decimal] Variance() + { + return $this.M2/($this.n - 1) + } + + [decimal] StdDev() + { + return [math]::Sqrt($this.Variance()) + } + + [decimal] Skew() + { + return [math]::Sqrt($this.n) * $this.M3 / [math]::Pow($this.M2, 1.5) + } + + [decimal] Kurtosis() + { + return (($this.n * $this.M4) / ($this.M2 * $this.M2)) - 3 + } +} + +# populate a hash by sbl device number +$dev = gwmi -Namespace root\wmi ClusPortDeviceInformation +$devhash = @{} +$dev |% { $devhash[[int]$_.devicenumber] = $_ } + +function get-outliers( + [string[]] $paths + ) +{ + # filters and labels for the sigma buckets + $sigflt = @(@(">5", '$sig -gt 5'), + @("4-5", '$sig -gt 4 -and $sig -le 5'), + @("3-4", '$sig -gt 3 -and $sig -le 4')) |% { + + new-object -TypeName psobject -Property @{ + Label = $_[0]; + Test= $_[1]; + } + } + + $ctrs = (get-counter -ComputerName (get-clusternode |? State -eq Up) -SampleInterval $interval -Counter $($paths |% { "\Cluster Disk Counters(*)\$_" })).countersamples |? { $_.instancename -ne "_total" } + $stat = [RunningStat]::new() + + foreach ($path in $paths) { + + # select out the specific subset of the counters (read, write, etc.) + $ctr = $ctrs |? { $_.Path -like "*$path" } + + $stat.Clear() + $ctr.CookedValue |% { $stat.Add($_) } + $stddev = $stat.StdDev() + $mean = $stat.Mean() + + # hash of flagged devices + $flagged = @{} + + write-host -ForegroundColor Green ("-"*20) + write-host -ForegroundColor green Sample: $path + write-host Number of measured devices: $ctr.count + write-host Average of $("$path : {0:F3}ms" -f ($mean*1000)) + write-host Standard Deviation of $("{0:F3}ms" -f ($stddev*1000)) + + # enumerate from highest to lowest deviation of merit + foreach ($sigma in $sigflt) { + + # get new outliers at this deviation + $outlier = $ctr | sort -property InstanceName |? { + if ($stddev -eq 0) { $sig = 0 } else { $sig = (($_.CookedValue -$mean)/$stddev) } + -not $flagged[$_.InstanceName] -and (iex $sigma.Test) + } + + if ($outlier) { + + write-host -fore Yellow $sigma.Label sigma : $outlier.count total + $outlier |% { + + # remember we flagged this device already + $flagged[$_.InstanceName] = 1 + + $thisdev = $devhash[[int]$_.InstanceName] + + # parse source node + # \\foo\... -> 0 1 2=foo 3 ... + $sourcenode = ($_.Path -split '\\')[2] + + write-host -ForegroundColor Red $sourcenode SSB Device: $_.InstanceName`n`tAverage of $path $("{0:F3}ms ({1:F1} sigma)" -f ($_.CookedValue * 1000),(($_.CookedValue -$mean)/$stddev)) + write-host -ForegroundColor Red "`tConnected Node:" $thisdev.ConnectedNode + write-host -ForegroundColor Red "`tConnected Node Local PhysicalDisk Number:" $thisdev.ConnectedNodeDeviceNumber + write-host -ForegroundColor Red "`tModel | SerialNumber: $($thisdev.ProductId)` | $($thisdev.SerialNumber)" + } + } + } + } +} + +get-outliers -paths "Remote: Read Latency","Local: Read Latency","Remote: Write Latency","Local: Write Latency" \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/check-pause.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/check-pause.ps1 new file mode 100644 index 0000000..a353129 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/check-pause.ps1 @@ -0,0 +1,90 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +# script to check the state/health of pause in the fleet +# +# isactive - whether there is an active pause (independent of health) +# !isactive - whether there is an active pause and all VMs have responded to it +# +# true/false return + +param( + [switch] $isactive = $false + ) + +$pause = "C:\ClusterStorage\collect\control\flag\pause" +$pauseepoch = gc $pause -ErrorAction SilentlyContinue + +if ($pauseepoch -eq $null) { + write-host -fore red Pause not in force + return $false +} + +# if only performing the pause active check, done +if ($isactive) { + return $true +} + +# accumulate hash of pause flags mapped to current/stale state +$h = @{} + +dir $pause-* |% { + + $thispause = gc $_ -ErrorAction SilentlyContinue + if ($thispause -eq $pauseepoch) { + $pausetype = 'Current' + } else { + $pausetype = 'Stale' + } + + if ($_.name -match 'pause-(.+)\+(vm.+)') { + # 1 is CSV, 2 is VM name + $h[$matches[2]] = $pausetype + } else { + write-host -fore red ERROR: malformed pause $_.name present + } +} + +# now correlate to online vms and see if we agree all online are paused. +# note that if we shutdown some vms and then check pause the current flags +# will be higher than online, so we need to verify individually to not +# spoof ourselves. + +$vms = get-clustergroup |? GroupType -eq VirtualMachine |? Name -like 'vm-*' |? State -eq Online + +$pausedvms = $vms |? { $h[$_.Name] -eq 'Current' } + +if ($pausedvms.Count -eq $vms.Count) { + write-host -fore green OK: All $vms.count VMs paused +} else { + write-host -fore red WARNING: of "$($vms.Count)," still waiting on ($vms.Count - $pausedvms.Count) to acknowledge pause + + compare-object $vms $pausedvms -Property Name -PassThru | sort -Property Name | ft -AutoSize Name,OwnerNode | Out-Host + return $false +} + +return $true \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/check-vmfleet.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/check-vmfleet.ps1 new file mode 100644 index 0000000..ec4d07a --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/check-vmfleet.ps1 @@ -0,0 +1,126 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +param ($group = '*') + +$g = Get-ClusterGroup |? GroupType -eq VirtualMachine |? Name -like "vm-$group-*" + + +########################################################### +write-host -fore green State Pivot +$g | group -Property State -NoElement | sort -Property Name | ft -autosize + +########################################################### +write-host -fore green Host Pivot +$g | group -Property OwnerNode,State -NoElement | sort -Property Name | ft -autosize + +########################################################### +write-host -fore green Group Pivot +$g |% { + if ($_.Name -match "^vm-([^-]+)-") { + $_ | add-member -NotePropertyName Group -NotePropertyValue $matches[1] -PassThru + } +} | group -Property Group,State -NoElement | sort -Property Name | ft -AutoSize + +########################################################### +write-host -fore green IOPS Pivot +# build 5 orders of log steps +$logstep = 10,20,50 +$log = 1 +$logs = 1..5 |% { + + $logstep |% { $_ * $log } + $log *= 10 +} + +# build log step names; 0 is the > range catchall +$lognames = @{} +foreach ($step in $logs) { + + if ($step -eq $logs[0]) { + $lognames[$step] = "< $step" + } else { + $lognames[$step] = "$pstep - $($step - 1)" + } + $pstep = $step +} +$lognames[0] = "> $($logs[-1])" + +# now bucket up VMs by flow rates +$qosbuckets = @{} +$qosbuckets[0] = 0 +$logs |% { + $qosbuckets[$_] = 0 +} + +Get-StorageQoSFlow |% { + + $found = $false + foreach ($step in $logs) { + + if ($_.InitiatorIops -lt $step) { + $qosbuckets[$step] += 1; + $found = $true + break + } + } + # if not bucketed, it is greater than range + if (-not $found) { + $qosbuckets[0] += 1 + } +} + +# find min/max buckets with nonzero counts, by $logs index +# this lets us present a continuous range, with interleaved zeroes +$bmax = -1 +$bmin = -1 +foreach ($i in 0..($logs.Count - 1)) { + if ($qosbuckets[$logs[$i]]) { + + if ($bmin -lt 0) { + $bmin = $i + } + $bmax = $i + } +} +# raise max if we have > range +if ($qosbuckets[0]) { + $bmax = $logs.Count - 1 +} +$range = @($logs[$bmin..$bmax]) +# add > range if needed, at end +if ($qosbuckets[0]) { + $range += 0 +} + +$(foreach ($i in $range) { + + New-Object -TypeName psobject -Property @{ + Count = $qosbuckets[$i]; + IOPS = $lognames[$i] + } +}) | ft Count,IOPS \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/clear-pause.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/clear-pause.ps1 new file mode 100644 index 0000000..711852c --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/clear-pause.ps1 @@ -0,0 +1,38 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +$pause = "C:\ClusterStorage\collect\control\flag\pause" + +if (gi $pause -ErrorAction SilentlyContinue) { + write-host -fore green Clearing pause from $([string](gi $pause).LastWriteTime) + do { + del $pause -Force -ErrorAction SilentlyContinue + } while (-not $?) + # del $pause-* -ErrorAction SilentlyContinue +} else { + write-host -fore yellow Pause not set +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/create-vmfleet.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/create-vmfleet.ps1 new file mode 100644 index 0000000..088568e --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/create-vmfleet.ps1 @@ -0,0 +1,366 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +param( + [string]$BaseVHD = $(throw "please specify a base vhd"), + [int]$VMs = $(throw "please specify a number of vms per node csv"), + [string[]]$Groups = @(), + [string]$AdminPass = $(throw 'need admin password for autologin'), + [string]$Admin = 'administrator', + [string]$ConnectPass = $(throw 'need password for loopback host connection'), + [string]$ConnectUser = $(throw 'need username for loopback host connection'), + [validateset('CreateVMSwitch','CopyVHD','CreateVM','CreateVMGroup','AssertComplete')][string]$StopAfter, + [validateset('Force','Auto','None')][string]$Specialize = 'Auto', + [switch]$FixedVHD = $true, + [string[]]$Nodes = @() + ) + +function Stop-After($step) +{ + if ($stopafter -eq $step) { + write-host -ForegroundColor Green Stop after $step + return $true + } + return $false +} + +################## + +# validate existence of basevhd +if (!(test-path -path $BaseVHD)) { + throw "Base VHD $BaseVHD not found" +} + +if (Get-ClusterNode |? State -ne Up) { + throw "not all cluster nodes are up; please address before creating vmfleet" +} + +# if no nodes specified, use the entire cluster +if ($nodes.count -eq 0) { + $nodes = Get-ClusterNode +} + +# convert to fixed vhd(x) if needed +if ((get-vhd $basevhd).VhdType -ne 'Fixed' -and $fixedvhd) { + + # push dynamic vhd to tmppath and place converted at original + # note that converting a dynamic will leave a sparse hole on refs + # this is OK, since the copy will not copy the hole + $f = gi $basevhd + $tmpname = "tmp-$($f.Name)" + $tmppath = join-path $f.DirectoryName $tmpname + del -Force $tmppath -ErrorAction SilentlyContinue + ren $f.FullName $tmpname + + write-host -ForegroundColor Yellow "convert $($f.FullName) to fixed via $tmppath" + convert-vhd -Path $tmppath -DestinationPath $f.FullName -VHDType Fixed + if (-not $?) { + ren $tmppath $f.Name + throw "ERROR: could not convert $($f.fullname) to fixed vhdx" + } + + del $tmppath +} + +# Create the fleet vmswitches with a fixed IP at the base of the APIPA range +icm $nodes { + + if (-not (Get-VMSwitch -Name Internal -ErrorAction SilentlyContinue)) { + + New-VMSwitch -name Internal -SwitchType Internal + Get-NetAdapter |? DriverDescription -eq 'Hyper-V Virtual Ethernet Adapter' |? Name -eq 'vEthernet (Internal)' | New-NetIPAddress -PrefixLength 16 -IPAddress '169.254.1.1' + } +} | ft -AutoSize + +#### STOPAFTER +if (Stop-After "CreateVMSwitch") { + return +} + +# create $vms vms per each csv named as +# vm name is vm-<$group>-- + +icm $nodes -ArgumentList $stopafter,(Get-Command Stop-After) { + + param( [string]$stopafter, + $fn ) + + set-item -Path function:\$($fn.name) -Value $fn.definition + # workaround evaluation bug and make $stopafter evaluate in the session + $null = $stopafter + + function apply-specialization( $path ) + { + # all steps here can fail immediately without cleanup + + # error accumulator + $ok = $true + + # create run directory + + del -Recurse -Force z:\run -ErrorAction SilentlyContinue + mkdir z:\run + $ok = $ok -band $? + if (-not $ok) { + Write-Error "failed run directory creation for $vhdpath" + return $ok + } + + # autologon + $null = reg load 'HKLM\tmp' z:\windows\system32\config\software + $ok = $ok -band $? + $null = reg add 'HKLM\tmp\Microsoft\Windows NT\CurrentVersion\WinLogon' /f /v DefaultUserName /t REG_SZ /d $using:admin + $ok = $ok -band $? + $null = reg add 'HKLM\tmp\Microsoft\Windows NT\CurrentVersion\WinLogon' /f /v DefaultPassword /t REG_SZ /d $using:adminpass + $ok = $ok -band $? + $null = reg add 'HKLM\tmp\Microsoft\Windows NT\CurrentVersion\WinLogon' /f /v AutoAdminLogon /t REG_DWORD /d 1 + $ok = $ok -band $? + $null = reg add 'HKLM\tmp\Microsoft\Windows NT\CurrentVersion\WinLogon' /f /v Shell /t REG_SZ /d 'powershell.exe -noexit -command c:\users\administrator\launch.ps1' + $ok = $ok -band $? + $null = [gc]::Collect() + $ok = $ok -band $? + $null = reg unload 'HKLM\tmp' + $ok = $ok -band $? + if (-not $ok) { + Write-Error "failed autologon injection for $vhdpath" + return $ok + } + + # scripts + + copy -Force C:\ClusterStorage\collect\control\master.ps1 z:\run\master.ps1 + $ok = $ok -band $? + if (-not $ok) { + Write-Error "failed injection of specd master.ps1 for $vhdpath" + return $ok + } + + del -Force z:\users\administrator\launch.ps1 -ErrorAction SilentlyContinue + gc C:\ClusterStorage\collect\control\launch-template.ps1 |% { $_ -replace '__CONNECTUSER__',$using:connectuser -replace '__CONNECTPASS__',$using:connectpass } > z:\users\administrator\launch.ps1 + $ok = $ok -band $? + if (-not $ok) { + Write-Error "failed injection of launch.ps1 for $vhdpath" + return $err + } + + echo $vmspec > z:\vmspec.txt + $ok = $ok -band $? + if (-not $ok) { + Write-Error "failed injection of vmspec for $vhdpath" + return $ok + } + + # load files + $f = 'z:\run\testfile1.dat' + if (-not (gi $f -ErrorAction SilentlyContinue)) { + fsutil file createnew $f (10GB) + $ok = $ok -band $? + fsutil file setvaliddata $f (10GB) + $ok = $ok -band $? + } + if (-not $ok) { + Write-Error "failed creation of initial load file for $vhdpath" + return $ok + } + + return $ok + } + + function specialize-vhd( $vhdpath ) + { + $vhd = (gi $vhdpath) + $vmspec = $vhd.Directory.Name,$vhd.BaseName -join '+' + + # mount vhd and its largest partition + $o = Mount-VHD $vhd -NoDriveLetter -Passthru + if ($o -eq $null) { + Write-Error "failed mount for $vhdpath" + return $false + } + $p = Get-Disk -number $o.DiskNumber | Get-Partition | sort -Property size -Descending | select -first 1 + $p | Add-PartitionAccessPath -AccessPath Z: + + $ok = apply-specialization Z: + + Remove-PartitionAccessPath -AccessPath Z: -InputObject $p + Dismount-VHD -DiskNumber $o.DiskNumber + + return $ok + } + + $csvs = Get-ClusterSharedVolume + + # handle restore cases by mapping the csv to the friendly name of the volume + # don't rely on the csv name to contain this data + + $vh = @{} + Get-Volume |? FileSystem -eq CSVFS |% { $vh[$_.Path] = $_ } + + $csvs |% { + $v = $vh[$_.SharedVolumeInfo.Partition.Name] + if ($v -ne $null) { + $_ | Add-Member -NotePropertyName VDName -NotePropertyValue $v.FileSystemLabel + } + } + + foreach ($csv in $csvs) { + + if ($($using:groups).Length -eq 0) { + $groups = @( 'base' ) + } else { + $groups = $using:groups + } + + # identify the CSvs for which this node should create its VMs + # the trailing characters (if any) are the group prefix + if ($csv.VDName -match "^$env:COMPUTERNAME(?:-.+){0,1}") { + + foreach ($group in $groups) { + + if ($csv.VDName -match "^$env:COMPUTERNAME-([^-]+)$") { + $g = $group+$matches[1] + } else { + $g = $group + } + + foreach ($vm in 1..$using:vms) { + + $stop = $false + + $newvm = $false + $name = "vm-$g-$env:COMPUTERNAME-$vm" + $path = Join-Path $csv.SharedVolumeInfo.FriendlyVolumeName $name + + # place vhdx in subdirectory, per scvmm layout defaults + # $vhd = $path+".vhdx" + $vhd = join-path $path "$name.vhdx" + + # if the vm cluster group exists, we are already deployed + if (-not (Get-ClusterGroup -Name $name -ErrorAction SilentlyContinue)) { + + if (-not $stop) { + $stop = Stop-After "AssertComplete" + } + + if ($stop) { + + Write-Host -ForegroundColor Red "vm $name not deployed" + + } else { + + Write-Host -ForegroundColor Yellow "create vm $name @ metadata path $path with vhd $vhd" + + # create vm if not already done + # note that when restarting interrupted creation, the vm could have moved elsewhere + # under cluster control. + + $o = Get-ClusterGroup |? GroupType -eq VirtualMachine |? Name -eq $name + + if (-not $o) { + + # force re-specialization + $newvm = $true + + # if the cluster group doesn't exist, we're on the canonical node to create the vm + # if the vm exists, tear it down and refresh + + $o = get-vm -Name $name -ErrorAction SilentlyContinue + + if ($o) { + + # interrupted between vm creation and role creation; redo it + write-host "REMOVING vm $name for re-creation" + + if ($o.State -ne 'Off') { + Stop-VM -Name $name -Force -Confirm:$false + } + Remove-VM -Name $name -Force -Confirm:$false + + } else { + + # scrub and re-create the vm metadata path and vhd + rmdir -ErrorAction SilentlyContinue -Recurse $path + $null = mkdir -ErrorAction SilentlyContinue $path + + cp $using:basevhd $vhd + } + + #### STOPAFTER + if (-not $stop) { + $stop = Stop-After "CopyVHD" + } + + if (-not $stop) { + $o = New-VM -VHDPath $vhd -Generation 2 -SwitchName Internal -Path $path -Name $name + + # create A1 VM. use set-vmfleet to alter fleet sizing post-creation. + $o | Set-VM -ProcessorCount 1 -MemoryStartupBytes 1.75GB -StaticMemory + + # do not monitor the internal switch connection; this allows live migration + $o | Get-VMNetworkAdapter| Set-VMNetworkAdapter -NotMonitoredInCluster $true + } + } + + #### STOPAFTER + if (-not $stop) { + $stop = Stop-After "CreateVM" + } + + if (-not $stop) { + + # create clustered vm role and assign default owner node + $o | Add-ClusterVirtualMachineRole + Set-ClusterOwnerNode -Group $o.VMName -Owners $env:COMPUTERNAME + } + } + + } else { + Write-Host -ForegroundColor Green "vm $name already deployed" + } + + #### STOPAFTER + if (-not $stop) { + $stop = Stop-After "CreateVMGroup" + } + + if (-not $stop -or ($using:specialize -eq 'Force')) { + # specialize as needed + # auto only specializes new vms; force always; none skips it + if (($using:specialize -eq 'Auto' -and $newvm) -or ($using:specialize -eq 'Force')) { + write-host -fore yellow specialize $vhd + if (-not (specialize-vhd $vhd)) { + write-host -fore red "Failed specialize of $vhd, halting." + } + } else { + write-host -fore green skip specialize $vhd + } + } + } + } + } + } +} diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/demo.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/demo.ps1 new file mode 100644 index 0000000..8048fbc --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/demo.ps1 @@ -0,0 +1,72 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +param([int] $interval = 60) + +$cnodes = (Get-ClusterNode).Count +$cvms = (Get-ClusterGroup |? GroupType -eq 'VirtualMachine').Count/$cnodes + +# loop through the set of run scripts +$f = gi C:\clusterstorage\collect\control\run-demo-*.ps1 + +# qos policies to loop +$policy = 'SilverVM','GoldVM','PlatinumVM',$null,$null + +while ($true) { + + $null = & update-csv.ps1 + + foreach ($run in $f) { + + cls + + # touch the current run file to trigger VM updates + # see the master script + (gi $run).LastWriteTime = (get-date) + + # pull the show-me line of the run script (comment, trimmed) + # format is a single line, with # standing in for newline for multi-line output + # substitute in the configuration's node and vms/node count + # TBD: autodetect vd configuration + $comm = (gc $run | sls '^#-#').Line + + write-host -fore green $($comm.trimstart('#- ') -replace '__CVMS__',$cvms -replace '__CNODES__',$cnodes -replace '#',"`n") + + # apply random QoS policy + $p = $policy[(0..($policy.count - 1) | Get-Random)] + $null = & set-storageqos.ps1 -policyname $p 2>&1 + + write-host -fore yellow `nActive QoS Policy + if ($p) { + Get-StorageQosPolicy -Name $p | ft -AutoSize + } else { + write-host NONE + } + + sleep $interval + } +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/destroy-vmfleet.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/destroy-vmfleet.ps1 new file mode 100644 index 0000000..281ee7a --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/destroy-vmfleet.ps1 @@ -0,0 +1,102 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +param( + [string[]] $vms = $null + ) + +if ($vms) { + write-host -ForegroundColor Yellow "Destroying VM Fleet content for: $vms" +} else { + write-host -ForegroundColor Yellow "Destroying VM Fleet" +} + +# stop and remove clustered vm roles +write-host -ForegroundColor Green "Removing VM ClusterGroups" +Get-ClusterGroup |? GroupType -eq VirtualMachine |? { + if ($vms) { + $_.Name -in $vms + } else { + $_.Name -like 'vm-*' + } +} |% { + write-host "Removing ClusterGroup for $($_.Name)" + $_ | Stop-ClusterGroup + $_ | Remove-ClusterGroup -RemoveResources -Force +} + +# remove all vms +write-host -ForegroundColor Green "Removing VMs" +icm (Get-ClusterNode) { + Get-VM |? { + if ($using:vms) { + $_.Name -in $using:vms + } else { + $_.Name -like 'vm-*' + } + } |% { + write-host "Removing VM for $($_.Name) @ $($env:COMPUTERNAME)" + $_ | Remove-VM -Confirm:$false -Force + } + + # do not remove the internal switch if teardown is partial + if ($using:vms -eq $null) { + write-host -ForegroundColor Green "Removing Internal VMSwitch" + Get-VMSwitch -SwitchType Internal | Remove-VMSwitch -Confirm:$False -Force + } +} + +# handle restore cases by mapping the csv to the friendly name of the volume +# don't rely on the csv name to contain this data +$csv = Get-ClusterSharedVolume + +$vh = @{} +Get-Volume |? FileSystem -eq CSVFS |% { $vh[$_.Path] = $_ } + +$csv |% { + $v = $vh[$_.SharedVolumeInfo.Partition.Name] + if ($v -ne $null) { + $_ | Add-Member -NotePropertyName VDName -NotePropertyValue $v.FileSystemLabel + } +} + +# now delete content from csvs corresponding to the cluster nodes +write-host -ForegroundColor Green "Removing CSV content for VMs" +Get-ClusterNode |% { + $csv |? VDName -match "$($_.Name)(-.+)?" +} |% { + dir -Directory $_.sharedvolumeinfo.friendlyvolumename |? { + if ($vms) { + $_.Name -in $vms + } else { + $_.Name -like 'vm-*' + } + } |% { + write-host "Removing CSV content for $($_.BaseName) @ $($_.FullName)" + del -Recurse -Force $_.FullName + } +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/get-cluspc.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/get-cluspc.ps1 new file mode 100644 index 0000000..889e01d --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/get-cluspc.ps1 @@ -0,0 +1,281 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +## +## netsh advfirewall firewall set rule group="Performance Logs and Alerts" new enable=yes +## +[CmdletBinding( DefaultParameterSetName = "BySeconds" )] +param( + + [Parameter( + ParameterSetName = 'BySeconds', + ValueFromPipeline = $false, + ValueFromPipelineByPropertyName = $false, + Mandatory = $false)] + [ValidateRange(1,[int]::MaxValue)] + [int] $Seconds = 1, + + [Parameter( + ParameterSetName = 'ByStart', + ValueFromPipeline = $false, + ValueFromPipelineByPropertyName = $false, + Mandatory = $false)] + [switch] $Start, + + [Parameter( + ParameterSetName = 'ByStop', + ValueFromPipeline = $false, + ValueFromPipelineByPropertyName = $false, + Mandatory = $false)] + [switch] $Stop = $false, + + <# --------- common #> + + [Parameter( + ParameterSetName = 'BySeconds', + Mandatory = $false)] + [Parameter( + ParameterSetName = 'ByStart', + Mandatory = $false)] + [Parameter( + ParameterSetName = 'ByStop', + Mandatory = $false)] + [string] $Cluster = ".", + + <# --------- start-time parameters #> + + [Parameter( + ParameterSetName = 'BySeconds', + Mandatory = $false)] + [Parameter( + ParameterSetName = 'ByStart', + Mandatory = $false)] + [ValidateSet('PhysicalDisk','CPU','SMB','SMBD','SSB','SSB Cache','CSVFS','Storage','Spaces')] + [string[]] $Set = '*', + + [Parameter( + ParameterSetName = 'BySeconds', + Mandatory = $false)] + [Parameter( + ParameterSetName = 'ByStart', + Mandatory = $false)] + [string] $AddSpec, + + [Parameter( + ParameterSetName = 'BySeconds', + Mandatory = $false)] + [Parameter( + ParameterSetName = 'ByStart', + Mandatory = $false)] + [int] $SampleInterval = 1, + + <# --------- stop-time parameters #> + + [Parameter( + ParameterSetName = 'BySeconds', + Mandatory = $false)] + [Parameter( + ParameterSetName = 'ByStop', + Mandatory = $false)] + [switch] $Force = $false, + + [Parameter( + ParameterSetName = 'BySeconds', + Mandatory = $true)] + [Parameter( + ParameterSetName = 'ByStop', + Mandatory = $true)] + [string] $Destination + ) + +if ($psCmdlet.ParameterSetName -ne 'ByStart' -and + (gi -ErrorAction SilentlyContinue $Destination)) { + + if (-not $force) { + Write-Error "$Destination already exists, please delete or use -Force to overwrite" + return + } else { + del -ErrorAction SilentlyContinue $Destination + } +} + +$sets = @{ + 'PhysicalDisk' = '\PhysicalDisk(*)\*','+getclusport'; + 'CSVFS' = '\Cluster CSVFS(*)\*','\Cluster CSV Volume Cache(*)\*','\Cluster CSV Volume Manager(*)\*','\Cluster CSVFS Block Cache(*)\*','\Cluster CSVFS Direct IO(*)\*','\Cluster CSVFS Redirected IO(*)\*','+getcsv'; + 'SSB' = '\Cluster Disk Counters(*)\*','+getclusport'; + 'SMB' = '\SMB Client Shares(*)\*','\SMB Server Shares(*)\*'; + 'SMBD' = '\SMB Direct Connection(*)\*'; + 'SSB Cache' = '\Cluster Storage Hybrid Disks(*)\*','\Cluster Storage Cache Stores(*)\*'; + 'Spaces' = '\Storage Spaces Write Cache(*)\*','\Storage Spaces Tier(*)\*','\Storage Spaces Virtual Disk(*)\*' + 'ReFS' = '\ReFS(*)\*'; + 'CPU' = '\Hyper-V Hypervisor Logical Processor(*)\*','\Processor Information(*)\*'; + + 'Storage' = 'PhysicalDisk','CSVFS','SSB','SSB Cache','ReFS','Spaces'; +} + +$special = @{ + "getclusport" = $false; + "getcsv" = $false; +} + +$cleanup = @() + +$pc = @() +if ($Set.count -eq 1 -and $Set[0] -eq '*') { + # list of all keys + $pc = $sets.keys |% { $_ } +} else { + $pc = $Set +} + +# repeat expansion (sets in sets, possibly containing special collection rules) +do { + $expansion = $false + $pc = $pc |% { + $n = $_ + switch ($n[0]) { + # performance counter - passthru + '\' { $n } + # special collection (sets variable -> $true) + '+' { + + if ($special.ContainsKey($n.Substring(1))) { + $special[$n.Substring(1)] = $true + } else { + throw "unrecognized special gather command $($n.Substring(1))" + } + } + # set expansion + default { + $sets[$n] + $expansion = $true + } + } + } +} while ($expansion) + +# uniq the counters +$pc = ($pc | group -NoElement).Name + +# -- + +function start-logman( + [string] $name, + [string[]] $counters, + [int] $sampleinterval + ) +{ + $computer = $env:COMPUTERNAME + $f = "c:\perfctr-$name-$computer.blg" + + $null = logman create counter "perfctr-$name" -o $f -f bin -si $sampleinterval --v -c $counters + $null = logman start "perfctr-$name" + write-host "performance counters on: $computer" +} + +function stop-logman( + [string] $name + ) +{ + $computer = $env:COMPUTERNAME + $f = "c:\perfctr-$name-$computer.blg" + + $null = logman stop "perfctr-$name" + $null = logman delete "perfctr-$name" + write-host "performance counters off: $computer" + + echo $("\\$computer\$f" -replace ':','$') +} + +if (-not $Stop) { + + icm (get-clusternode -cluster $Cluster |? State -eq Up) -ArgumentList (get-command start-logman) { + + param($fn) + set-item -path function:\$($fn.name) -value $fn.definition + + start-logman $using:AddSpec $using:pc -sampleinterval $using:SampleInterval + } + + if ($Start) { + write-host -ForegroundColor Yellow INFO: captures started - reinvoke with `-stop to complete capture + return + } + + sleep $Seconds + +} else { + + write-host -ForegroundColor Yellow INFO: completing previously started capture +} + +# now capture all counter files +$f = @() +$f += icm (get-clusternode -cluster $Cluster |? State -eq Up) -ArgumentList (get-command stop-logman) { + + param($fn) + set-item -path function:\$($fn.name) -value $fn.definition + + stop-logman $using:AddSpec $using:Destination +} + +# add all counter blg to the cleanup step +$cleanup += $f + +#-- +# specials +#-- + +# make capture directory, and add to cleanup list +# note that all specials are generated into this directory, +# and will be automatically cleaned up when it is deleted +$t = New-TemporaryFile +del $t +$null = md $t +$cleanup += $t + +if ($special['getclusport']) { + + get-clusternode -cluster $Cluster |? State -eq Up |% { + + $exp = "$t\clusport-$_.xml" + gwmi -ComputerName $_ -Namespace root\wmi ClusPortDeviceInformation | export-clixml -Path $exp + $f += $exp + } +} + +if ($special['getcsv']) { + + $exp = "$t\csv.xml" + Get-ClusterSharedVolume -cluster $Cluster | export-clixml -Path $exp + $f += $exp +} + +compress-archive -DestinationPath $Destination -Path $f +del -Force -Recurse $cleanup + +write-host "performance counters: $Destination" \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/get-linfit.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/get-linfit.ps1 new file mode 100644 index 0000000..f313e62 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/get-linfit.ps1 @@ -0,0 +1,143 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +param( + $csvfile, + [string]$delimeter = "`t", + [string]$xcol = $(throw "please specify column containing x values"), + [string]$ycol = $(throw "please specify column containing y values"), + [string[]]$idxcol, + [switch]$zerointercept = $false + ) + +# given the input data, produce the linear fit coefficients for +# +# zerointercept = true: y = bx +# !zerointercept = true: y = a + bx +# +# specify the column containing the x and y measurements +# a single index column can be used to differentiate rows which should be +# used for the fit (i.e., "test1", "test2", ...). +# +# method: ordinary least squares + +function do-linfit( + [string] $xcol, + [string] $ycol, + [string] $key + ) +{ + BEGIN + { + $sumxy = [decimal]0 + $sumx2 = [decimal]0 + $sumx = [decimal]0 + $sumy = [decimal]0 + + $n = 0 + + $pipe = @() + } + + PROCESS + { + $x = [decimal]$_.$xcol + $y = [decimal]$_.$ycol + + $sumxy += $x*$y + $sumx2 += $x*$x + $sumx += $x + $sumy += $y + + $n += 1 + + # accumulate pipeline for second pass + $pipe += $_ + } + + END + { + if ($n -eq 0) { + Write-Error "ERROR: no measurements matched" + return + } + + # perform requested fit + + $a = 0 + $b = 0 + + if ($zerointercept) { + + $a = 0 + $b = ($sumxy/$sumx2) + + } else { + + $b = ($sumxy - (($sumx*$sumy)/$n)) / ($sumx2 - (($sumx*$sumx)/$n)) + $a = ($sumy - $b*$sumx)/$n + } + + # calculate r2 (coefficient of determination) with respect to the fit + $meany = $sumy/$n + + # total sum of squares + $sstot = [decimal]0 + $pipe |% { + $v = [decimal]$_.$ycol - $meany + $sstot += $v*$v + } + + # residual sum of squares + $ssres = [decimal]0 + $pipe |% { + $v = [decimal]$_.$ycol - ($a + $b*[decimal]$_.$xcol) + $ssres += $v*$v + } + + $r2 = 1 - ($ssres/$sstot) + + new-object -TypeName psobject -Property @{ 'A' = $a; 'B' = $b; 'R2' = $r2; 'N' = $n; 'Key' = $key; 'X' = $xcol; 'Y' = $ycol } + } +} + +# process the results into a hash keyed by the index columns +$h = @{} +import-csv -Path $csvfile -Delimiter $delimeter |% { + + $key = "" + foreach ($col in $idxcol) { + $key += "$col $($_.$col)" + } + + $h[$key] += ,$_ +} + +$h.Keys |% { + + $h[$_] | do-linfit -xcol $xcol -ycol $ycol -key $_ +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/get-log.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/get-log.ps1 new file mode 100644 index 0000000..33d0118 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/get-log.ps1 @@ -0,0 +1,80 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +param( + [string] $zip = $(throw "please provide zip filename for compressed logs"), + [ValidateSet("*","HyperV","FailoverClustering","SMB")][string[]] $set = "*", + [int] $timespan = 0 + ) + +# convert minutes to milliseconds +if ($timespan -gt 0) { + $timespan *= 60*1000 +} + +$logs = icm (get-clusternode) { + + $map = @{ + "HyperV" = "Microsoft-Windows-Hyper-V"; + "FailoverClustering" = "Microsoft-Windows-FailoverClustering" + "SMB" = "Microsoft-Windows-SMB" + } + + if ($using:set -eq "*") { + $lset = $map.Keys |% { $_ } + } else { + $lset = $using:set + } + +$q = @" +/q:"*[System[TimeCreated[timediff(@SystemTime) <= __TIME__]]]" +"@ + + if ($using:timespan -gt 0) { + $timefilt = $q -replace '__TIME__',$using:timespan + } else { + $timefilt = $null + } + + $lset |% { + + # get logs and normalize to legal filenames + $prov = wevtutil el | sls $map[$_] + $provf = $prov -replace '/','-' + + foreach ($i in 0..($prov.Count - 1)) { + + $localpath = "$($provf[$i])--$env:computername.evtx" + del -Force $localpath -ErrorAction SilentlyContinue + wevtutil epl $prov[$i] "c:\$localpath" $timefilt + write-output "\\$env:computername\c$\$localpath" + } + } +} + +compress-archive -Path $logs -DestinationPath $zip +del -force $logs \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/install-vmfleet.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/install-vmfleet.ps1 new file mode 100644 index 0000000..469237e --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/install-vmfleet.ps1 @@ -0,0 +1,74 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +param( + $source = $(throw "Must specify the source directory for the vmfleet scripts") + ) + +$col = get-clustersharedvolume |? Name -match '(collect)' +if ($col -eq $null) { + write-error "The collect CSV is not present" + return +} + +$fqsrc = gi $source +if ($fqsrc -eq $null) { + write-error "The source directory for the vmfleet scripts is not accessible" +} + +# update the csv mounts to their normalized names and install scripts and directory structure +# remove internet-download blocks on ps1 scripting, if present +& $fqsrc\update-csv.ps1 -renamecsvmounts +$control = 'C:\ClusterStorage\collect\control' + +cp -r $fqsrc $control +dir $control\*.ps1 |% { Unblock-File $_ } + +mkdir $control\result +mkdir $control\flag +mkdir $control\tools + + +# put the control directory onto the path +if (-not ([System.Environment]::GetEnvironmentVariable("path") -split ';' |? { $_ -eq $control })) { + $newpath = [System.Environment]::GetEnvironmentVariable("path") + ";$control" + [System.Environment]::SetEnvironmentVariable("path", + $newpath, + [System.EnvironmentVariableTarget]::Process) + [System.Environment]::SetEnvironmentVariable("path", + $newpath, + [System.EnvironmentVariableTarget]::User) +} + +# disable the csv balancer so that motion is under fleet control +(get-cluster).CsvBalancer = 0 + +# finally set fleet pause and touch the base run file so that we ensure +# the fleet will start with it. +set-pause +(gi $control\run.ps1).IsReadOnly = $false +(gi $control\run.ps1).LastWriteTime = (Get-Date) \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/launch-template.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/launch-template.ps1 new file mode 100644 index 0000000..439df37 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/launch-template.ps1 @@ -0,0 +1,34 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +$script = 'c:\run\master.ps1' + +while ($true) { + Write-Host -fore Green Launching $script `@ $(Get-Date) + & $script -connectuser __CONNECTUSER__ -connectpass __CONNECTPASS__ + sleep -Seconds 1 +} diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/master.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/master.ps1 new file mode 100644 index 0000000..98bc673 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/master.ps1 @@ -0,0 +1,225 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +# +# VM Master Script +# +# This script must be executed on autologon by the VM. It establishes a mapping a location containing the Run Script +# and then repeatedly polls/executes the latest one it finds. This allows the runner to inject new run scripts on the +# fly and/or touch pre-existing ones to bump them to the top of the list. +# +# In the initial form, it assumes the VM is connected by an internal switch and it just polls all the node names to +# find the one which will work. This could be improved by using specialization to inject the specific path to a +# configuration file. +# + +param( + [string] $connectuser, + [string] $connectpass + ) + +$null = net use l: /d + +if ($(Get-SmbMapping) -eq $null) { + New-SmbMapping -LocalPath l: -RemotePath \\169.254.1.1\c$\clusterstorage\collect\control -UserName $connectuser -Password $connectpass -ErrorAction SilentlyContinue +} + +# update tooling +cp l:\tools\* c:\run -Force + +$run = 'c:\run\run.ps1' +$master = 'c:\run\master.ps1' + +$mypause = "l:\flag\pause-$(gc c:\vmspec.txt)" +$mydone = "l:\flag\done-$(gc c:\vmspec.txt)" + +$done = $false +$donetouched = $false +$pause = $false + +$tick = 0 + +function get-newfile( + $sourcepat, + $dest, + $clean = $false, + $silent = $false + ) +{ + $sf = dir $sourcepat | sort -Property LastWriteTime -Descending | select -first 1 + $df = gi $dest -ErrorAction SilentlyContinue + + # no source? + if ($sf -eq $null) { + write-host -fore green NO source present + # clean if needed + if ($clean) { + rm $dest + } + # no new file is an update condition + $true + } elseif ($df -eq $null -or $sf.lastwritetime -gt $df.lastwritetime) { + write-host -fore green NEWER source $sf.fullname '=>' $dest + # update with newer source file and indicate update condition + cp $sf.fullname $dest -Force + $true + } else { + if (-not $silent) { + write-host -fore green NO newer source $sf.lastwritetime $sf.fullname '**' $df.LastWriteTime $df.fullname + } + # already have latest, indicate no new + $false + } +} + +function get-flagfile( + [string] $flag, + [switch] $gc = $false + ) +{ + if ($gc) { + gc "l:\flag\$flag" -ErrorAction SilentlyContinue + } else { + gi "l:\flag\$flag" -ErrorAction SilentlyContinue + } +} + +while ($true) { + + # update master control? + # assume runners have a simple loop to re-execute on exit + if (get-newfile l:\master*.ps1 $master -silent:$true) { + break + } + + # check and acknowledge pause - only drop flag once + $pauseepoch = get-flagfile pause -gc:$true + if ($pauseepoch -ne $null) { + + # pause clears done + $done = $false + + # drop into pause flagfile if needed + if ($pause -eq $false) { + write-host -fore red PAUSE IN FORCE + $pause = $true + echo $pauseepoch > $mypause + } else { + if ($tick % 10 -eq 0) { + write-host -fore red -NoNewline '.' + } + } + + } elseif ($done) { + + # drop epoch into done flagfile if needed + if (-not $donetouched) { + echo $goepoch > $mydone + $donetouched = $true + } + + # if go flag is now different, release for the next go around + if ($goepoch -ne (get-flagfile go -gc:$true)) { + $done = $false + write-host -fore cyan `nReleasing from Done + } else { + if ($tick % 10 -eq 0) { + write-host -fore yellow -NoNewline '.' + } + } + + } else { + + # clear pause & donetouched flags + $pause = $false + $donetouched = $false + + # update run script? + $null = get-newfile l:\run*.ps1 $run -clean:$true + $runf = gi $run -ErrorAction SilentlyContinue + + if ($runf -eq $null) { + write-host -fore yellow no control file found + sleep 30 + continue + } + + # update go epoch - a change to this (if present) will be what + # allows us to proceed past a done flag + $goepoch = get-flagfile go -gc:$true + if ($goepoch -ne $null) { + write-host -fore cyan Go Epoch acknowledged at: $goepoch + } + + # acknowledge script + write-host -fore cyan Run Script $runf.lastwritetime "`n$("*"*20)" + gc $runf + write-host -fore cyan ("*"*20) + + # launch and monitor pause and new run file + $j = start-job -arg $run { param($run) & $run } + while (($jf = wait-job $j -Timeout 1) -eq $null) { + + $halt = $null + + # check pause or new run file: if so, stop and loop + if (get-flagfile pause) { + $halt = "pause set" + } + + if (get-newfile l:\run*.ps1 $run -clean:$true -silent:$true) { + $halt = "new run file" + } + + if ($halt -ne $null) { + write-host -fore yellow STOPPING CURRENT "(reason: $halt)" + $j | stop-job + $j | remove-job + break + } + } + + # job finished? + if ($jf -ne $null) { + $result = $jf | receive-job + + if ($result -eq "done") { + write-host -fore yellow DONE CURRENT + $done = $true + } + + $jf | remove-job + } + + write-host -fore cyan ("*"*20) + } + + # force gc to teardown potentially conflicting handles and enforce min pause + [system.gc]::Collect() + sleep 1 + $tick += 1 +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/run-demo-100r.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/run-demo-100r.ps1 new file mode 100644 index 0000000..520f046 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/run-demo-100r.ps1 @@ -0,0 +1,39 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +#-# System Config: __CNODES__ Systems x __CVMS__ VMs/System#Storage Config: 3-way S2D Mirror#Current Workload: 100% 4K Random Read + +[string](get-date) + +$b = 4 +$t = 8 +$o = 20 +$w = 0 + +C:\run\diskspd.exe -n -h `-t$t `-o$o `-b$($b)k `-r$($b)k `-w$w -W10 -d60 -C10 -D -L (dir C:\run\testfile?.dat) + +[string](get-date) diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/run-demo-7030.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/run-demo-7030.ps1 new file mode 100644 index 0000000..ae22f5b --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/run-demo-7030.ps1 @@ -0,0 +1,38 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +#-# System Config: __CNODES__ Systems x __CVMS__ VMs/System#Storage Config: 3-way S2D Mirror#Current Workload: 70:30 4K Random Read/Write +[string](get-date) + +$b = 4 +$t = 8 +$o = 20 +$w = 30 + +C:\run\diskspd.exe -n -h `-t$t `-o$o `-b$($b)k `-r$($b)k `-w$w -W10 -d60 -C10 -D -L (dir C:\run\testfile?.dat) + +[string](get-date) diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/run-demo-9010.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/run-demo-9010.ps1 new file mode 100644 index 0000000..36e4986 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/run-demo-9010.ps1 @@ -0,0 +1,38 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +#-# System Config: __CNODES__ Systems x __CVMS__ VMs/System#Storage Config: 3-way S2D Mirror#Current Workload: 90:10 4K Random Read/Write +[string](get-date) + +$b = 4 +$t = 8 +$o = 20 +$w = 10 + +C:\run\diskspd.exe -n -h `-t$t `-o$o `-b$($b)k `-r$($b)k `-w$w -W10 -d60 -C10 -D -L (dir C:\run\testfile?.dat) + +[string](get-date) diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/run-sweeptemplate.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/run-sweeptemplate.ps1 new file mode 100644 index 0000000..2b3ede8 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/run-sweeptemplate.ps1 @@ -0,0 +1,78 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +[string](get-date) + +# buffer size/alighment, threads/target, outstanding/thread, write% +$b = __b__; $t = __t__; $o = __o__; $w = __w__ + +# optional - specify rate limit in iops, translated to bytes/ms for DISKSPD +$iops = __iops__ +if ($iops -ne $null) { $g = $iops * $b * 1KB / 1000 } + +# io pattern, (r)andom or (s)equential (si as needed for multithread) +$p = '__p__' + +# durations of test, cooldown, warmup +$d = __d__; $cool = __Cool__; $warm = __Warm__ + +# sweep template always captures +$addspec = '__AddSpec__' +$gspec = $null +if ($g -ne $null) { $gspec = "g$($g)" } +$result = "result-b$($b)t$($t)o$($o)w$($w)p$($p)$($gspec)-$($addspec)-$(gc c:\vmspec.txt).xml" +$dresult = "l:\result" +$lresultf = join-path "c:\run" $result +$dresultf = join-path $dresult $result + +### prior to this is template + +if (-not (gi $dresultf -ErrorAction SilentlyContinue)) { + + $res = 'xml' + $gspec = $null + if ($g -ne $null) { $gspec = "-g$($g)" } + + C:\run\diskspd.exe -Z20M -z -h `-t$t `-o$o $gspec `-b$($b)k `-$($p)$($b)k `-w$w `-W$warm `-C$cool `-d$($d) -D -L `-R$res (dir C:\run\testfile?.dat) > $lresultf + + # export result and indicate done flag to master + # use unbuffered copy to force IO downstream + xcopy /j $lresultf $dresult + del $lresultf + Write-Output "done" + +} else { + + write-host -fore green already done $dresultf + + # indicate done flag to master + # this should only occur if controller does not change variation + Write-Output "done" +} + +[system.gc]::Collect() +[string](get-date) \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/run.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/run.ps1 new file mode 100644 index 0000000..285e296 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/run.ps1 @@ -0,0 +1,94 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +[string](get-date) + +# buffer size/alighment, threads/target, outstanding/thread, write% +$b = 4; $t = 1; $o = 8; $w = 10 + +# optional - specify rate limit in iops, translated to bytes/ms for DISKSPD +$iops = 500 +if ($iops -ne $null) { $g = $iops * $b * 1KB / 1000 } + +# io pattern, (r)andom or (s)equential (si as needed for multithread) +$p = 'r' + +# durations of test, cooldown, warmup +$d = 30*60; $cool = 30; $warm = 60 + +$addspec = 'base' +$gspec = $null +if ($g -ne $null) { $gspec = "g$($g)" } +$result = "result-b$($b)t$($t)o$($o)w$($w)p$($p)$($gspec)-$($addspec)-$(gc c:\vmspec.txt).xml" +$dresult = "l:\result" +$lresultf = join-path "c:\run" $result +$dresultf = join-path $dresult $result + +# cap -> true to capture xml results, otherwise human text +$cap = $false + +### prior to this is template + +if (-not (gi $dresultf -ErrorAction SilentlyContinue)) { + + if ($cap) { + $res = 'xml' + } else { + $res = 'text' + } + + $gspec = $null + if ($g -ne $null) { $gspec = "-g$($g)" } + $o = C:\run\diskspd.exe -Z20M -z -h `-t$t `-o$o $gspec `-b$($b)k `-$($p)$($b)k `-w$w `-W$warm `-C$cool `-d$($d) -D -L `-R$res (dir C:\run\testfile?.dat) + + if ($cap) { + + # export result and indicate done flag to master + # use unbuffered copy to force IO downstream + + $o | Out-File $lresultf -Encoding ascii -Width 9999 + xcopy /j $lresultf $dresult + del $lresultf + Write-Output "done" + + } else { + + #emit to human watcher + $o | Out-Host + } + +} else { + + write-host -fore green already done $dresultf + + # indicate done flag to master + # this should only occur if controller does not change variation + Write-Output "done" +} + +[system.gc]::Collect() +[string](get-date) \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/s2d-vmfleet.docx b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/s2d-vmfleet.docx new file mode 100644 index 0000000..656eb02 Binary files /dev/null and b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/s2d-vmfleet.docx differ diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/s2d-vmfleet.pdf b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/s2d-vmfleet.pdf new file mode 100644 index 0000000..34a0704 Binary files /dev/null and b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/s2d-vmfleet.pdf differ diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/set-pause.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/set-pause.ps1 new file mode 100644 index 0000000..9cb8c22 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/set-pause.ps1 @@ -0,0 +1,36 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +$pause = "C:\ClusterStorage\collect\control\flag\pause" +$p = gi $pause -ErrorAction SilentlyContinue + +if ($p) { + write-host -fore green Pause already set $p.CreationTime +} else { + echo (get-random) > $pause + write-host -fore red Pause set `@ (get-date) +} diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/set-storageqos.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/set-storageqos.ps1 new file mode 100644 index 0000000..270d1d8 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/set-storageqos.ps1 @@ -0,0 +1,54 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +# apply given QoS policy (by name) to all VMs on specified nodes +param ( + $policyname = $null, + [object[]] $node = $(get-clusternode |? State -eq Up) +) + +if ($policyname -ne $null) { + + # QoS policy must exist, else error out + $qosp = get-storageqospolicy -name $policyname + if ($qosp -eq $null) { + # cmdlet error sufficient + return + } + $id = $qosp.PolicyId + +} else { + + # clears QoS policy + $id = $null +} + +icm $node { + + # note: set-vhdqos should be replaced with set-vmharddiskdrive + get-vm |% { get-vmharddiskdrive $_ |% { Set-VMHardDiskDrive -QoSPolicyID $using:id -VMHardDiskDrive $_ }} +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/set-vmfleet.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/set-vmfleet.ps1 new file mode 100644 index 0000000..bfe60ba --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/set-vmfleet.ps1 @@ -0,0 +1,157 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +param( [Parameter(ParameterSetName = 'FullSpec', Mandatory = $true)] + [int] $ProcessorCount, + [Parameter(ParameterSetName = 'FullSpec', Mandatory = $true)] + [int64] $MemoryStartupBytes, + [Parameter(ParameterSetName = 'FullSpec')] + [int64] $MemoryMaximumBytes = 0, + [Parameter(ParameterSetName = 'FullSpec')] + [int64] $MemoryMinimumBytes = 0, + [Parameter(ParameterSetName = 'FullSpec')] + [switch]$DynamicMemory = $true, + [Parameter(ParameterSetName = 'SizeSpec', Mandatory = $true)] + [ValidateSet('A0','A1','A1v2','A2','A2mv2','A2v2','A3','A4','A4mv2','A4v2','A5','A6','A7','A8mv2','A8v2','D1','D11','D11v2','D12','D12v2','D13','D13v2','D14','D14v2','D1 +5v2','D1v2','D2','D2v2','D3','D3v2','D4','D4v2','D5v2','DS11','DS11v2','DS12','DS12v2','DS13','DS13v2','DS14','DS14v2','DS15v2')] + [string]$Compute = 'A1' + ) + +# to regenerate validateset +# ($vmsize.keys | sort |% { "'$_'" }) -join ',' + +# c = compute cores +# m = memory +# a = alias for another (ex: d -> ds, d -> dv2) +# note that alias is only chased once + + $vmsize = @{ + + # general purpose table + 'A0' = @{ 'c' = 1; 'm' = 0.75GB; }; + 'A1' = @{ 'c' = 1; 'm' = 1.75GB; }; + 'A2' = @{ 'c' = 2; 'm' = 3.5GB; }; + 'A3' = @{ 'c' = 4; 'm' = 7GB; }; + 'A4' = @{ 'c' = 8; 'm' = 14GB; }; + 'A5' = @{ 'c' = 2; 'm' = 14GB }; + 'A6' = @{ 'c' = 4; 'm' = 28GB }; + 'A7' = @{ 'c' = 8; 'm' = 56GB }; + + 'A1v2' = @{ 'c' = 1; 'm' = 2GB; }; + 'A2v2' = @{ 'c' = 2; 'm' = 4GB; }; + 'A4v2' = @{ 'c' = 4; 'm' = 8GB; }; + 'A8v2' = @{ 'c' = 8; 'm' = 16GB; }; + 'A2mv2' = @{ 'c' = 2; 'm' = 16GB; } + 'A4mv2' = @{ 'c' = 4; 'm' = 32GB; }; + 'A8mv2' = @{ 'c' = 8; 'm' = 64GB; }; + + 'D1' = @{ 'c' = 1; 'm' = 3.5GB }; + 'D2' = @{ 'c' = 2; 'm' = 7GB }; + 'D3' = @{ 'c' = 4; 'm' = 14GB }; + 'D4' = @{ 'c' = 8; 'm' = 28GB }; + + 'D1v2' = @{ 'a' = 'D1' } + 'D2v2' = @{ 'a' = 'D2' } + 'D3v2' = @{ 'a' = 'D3' } + 'D4v2' = @{ 'a' = 'D4' } + 'D5v2' = @{ 'c' = 16; 'm' = 56GB }; + + # memory optimized table (just the d's) + 'D11' = @{ 'c' = 2; 'm' = 14GB }; + 'D12' = @{ 'c' = 4; 'm' = 28GB }; + 'D13' = @{ 'c' = 8; 'm' = 56GB }; + 'D14' = @{ 'c' = 16; 'm' = 112GB }; + + 'DS11' = @{ 'a' = 'D11' }; + 'DS12' = @{ 'a' = 'D12' }; + 'DS13' = @{ 'a' = 'D13' }; + 'DS14' = @{ 'a' = 'D14' }; + + 'D11v2' = @{ 'a' = 'D11' }; + 'D12v2' = @{ 'a' = 'D12' }; + 'D13v2' = @{ 'a' = 'D13' }; + 'D14v2' = @{ 'a' = 'D14' }; + 'D15v2' = @{ 'c' = 20; 'm' = 140GB }; + + 'DS11v2' = @{ 'a' = 'D11v2' }; + 'DS12v2' = @{ 'a' = 'D12v2' }; + 'DS13v2' = @{ 'a' = 'D13v2' }; + 'DS14v2' = @{ 'a' = 'D14v2' }; + 'DS15v2' = @{ 'a' = 'D15v2' }; +} + +$g = Get-ClusterGroup |? GroupType -eq VirtualMachine | group -Property OwnerNode -NoElement + +icm $g.Name { + + # import vmsize hash (cannot $using[$using]) + $vmsize = $using:vmsize + + Get-ClusterGroup |? GroupType -eq VirtualMachine |? OwnerNode -eq $env:COMPUTERNAME |% { + + $g = $_ + + switch ($using:PSCmdlet.ParameterSetName) { + + 'FullSpec' { + + if ($using:MemoryMaximumBytes -eq 0) { + $MemoryMaximumBytes = $MemoryStartupBytes + } else { + $MemoryMaximumBytes = $using:MemoryMaximumBytes + } + if ($using:MemoryMinimumBytes -eq 0) { + $MemoryMinimumBytes = $MemoryStartupBytes + } else { + $MemoryMinimumBytes = $using:MemoryMinimumBytes + } + + $memswitch = '-StaticMemory' + $dynamicMemArg = "" + if ($using:DynamicMemory) { + $memswitch = '-DynamicMemory' + $dynamicMemArg += "-MemoryMinimumBytes $MemoryMinimumBytes -MemoryMaximumBytes $MemoryMaximumBytes" + } + + if ($g.State -ne 'Offline') { + write-host -ForegroundColor Yellow Cannot alter VM sizing on running VMs "($($_.Name))" + } else { + iex "Set-VM -ComputerName $($g.OwnerNode) -Name $($g.Name) -ProcessorCount $using:ProcessorCount -MemoryStartupBytes $using:MemoryStartupBytes $dynamicMemArg $memswitch" + } + } + + 'SizeSpec' { + $a = $vmsize[$using:compute].a + if ($a -eq $null) { + $a = $using:compute + } + + Set-VM -ComputerName $($g.OwnerNode) -Name $($g.Name) -ProcessorCount $vmsize[$a].c -MemoryStartupBytes $vmsize[$a].m -StaticMemory + } + } + } +} diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/start-sweep.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/start-sweep.ps1 new file mode 100644 index 0000000..ae3042c --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/start-sweep.ps1 @@ -0,0 +1,506 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +param( + [string] $addspec = "base", + [string] $runtemplate = "c:\clusterstorage\collect\control\run-sweeptemplate.ps1", + [string] $runfile = "c:\clusterstorage\collect\control\run-sweep.ps1", + [string[]] $labeltemplate = @('b','t','o','w','p','-$addspec'), + [Parameter(Mandatory =$true)] + [int[]] $b, + [Parameter(Mandatory =$true)] + [int[]] $t, + [Parameter(Mandatory =$true)] + [int[]] $o, + [Parameter(Mandatory =$true)] + [int[]] $w, + [int[]] $iops = $null, + [ValidateSet('r','s','si')] + [string[]] $p = 'r', + [ValidateRange(1,[int]::MaxValue)] + [int] $d = 60, + [ValidateRange(0,[int]::MaxValue)] + [int] $warm = 60, + [ValidateRange(0,[int]::MaxValue)] + [int] $cool = 60, + [string[]] $pc = $null, + [switch] $midcheck = $false + ) + +############# + +# a single named variable with a set of values +class variable { + + [int] $_ordinal + [object[]] $_list + [string] $_label + + variable([string] $label, [object[]] $list) + { + $this._list = $list + $this._ordinal = 0 + $this._label = $label + } + + # current value of the variable ("red"/"blue"/"green") + [object] value() + { + if ($this._list.count -gt 0) { + return $this._list[$this._ordinal] + } else { + return $null + } + } + + # label/name of the variable ("color") + [object] label() + { + return $this._label + } + + # increment to the next member, return whether a logical carry + # has occured (overflow) + [bool] increment() + { + # empty list passes through + if ($this._list.Count -eq 0) { + return $true + } + + # non-empty list, increment + $this._ordinal += 1 + if ($this._ordinal -eq $this._list.Count) { + $this._ordinal = 0 + return $true + } + return $false + } + + # back to initial state + [void] reset() + { + $this._ordinal = 0 + } +} + +# a set of variables which allows enumeration of their combinations +# this behaves as a numbering system indexing the respective variables +# order is not specified +class variableset { + + [hashtable] $_set = @{} + + variableset([variable[]] $list) + { + $list |% { $this._set[$_.label()] = $_ } + $this._set.Values |% { $_.reset() } + } + + # increment the enumeration + # returns true if the enumeration is complete + [bool] increment() + { + $carry = $false + foreach ($v in $this._set.Values) { + + # if the variable returns the carry flag, increment + # the next, and so forth + $carry = $v.increment() + if (-not $carry) { break } + } + + # done if the most significant carried over + return $carry + } + + # enumerator of all variables + [object] getset() + { + return $this._set.Values + } + + # return value of specific variable + [object] get([string]$label) + { + return $this._set[$label].value() + } + + # return a label representing the current value of the set, following the input label template + # add a leading '-' to get a seperator + # use a leading '$' to eliminate repetition of label (just produce value) + [string] label([string[]] $template) + { + return $($template |% { + + $str = $_ + $pfx = '' + $done = $false + $norep = $false + do { + switch ($str[0]) + { + '-' { + $pfx = '-' + $str = $str.TrimStart('-') + } + '$' { + $norep = $true + $str = $str.TrimStart('$') + } + default { + $done = $true + } + } + } while (-not $done) + + $lookstr = $str + if ($norep) { + $str = '' + } + + # only produce labels for non-null values + if ($this._set[$lookstr].value() -ne $null) { + "$pfx$str" + $this._set[$lookstr].value() + } + }) -join $null + } +} + +############# + +function start-logman( + [string] $computer, + [string] $name, + [string[]] $counters + ) +{ + $f = "c:\perfctr-$name-$computer.blg" + + $null = logman create counter "perfctr-$name" -o $f -f bin -si 1 --v -c $counters -s $computer + $null = logman start "perfctr-$name" -s $computer + write-host "performance counters on: $computer" +} + +function stop-logman( + [string] $computer, + [string] $name, + [string] $path + ) +{ + $f = "c:\perfctr-$name-$computer.blg" + + $null = logman stop "perfctr-$name" -s $computer + $null = logman delete "perfctr-$name" -s $computer + xcopy /j $f $path + del -force $f + write-host "performance counters off: $computer" +} + +function new-runfile( + [variableset] $vs + ) +{ + # apply current subsitutions to produce a new run file + gc $runtemplate |% { + + $line = $_ + + foreach ($v in $vs.getset()) { + # non-null goes in as is, null goes in as evaluatable $null + if ($v.value() -ne $null) { + $vsub = $v.value() + } else { + $vsub = '$null' + } + $line = $line -replace "__$($v.label())__",$vsub + } + + $line + + } | out-file $runfile -Encoding ascii -Width 9999 +} + +function show-run( + [variableset] $vs + ) +{ + # show current substitions (minus the underscore bracketing) + write-host -fore green RUN SPEC `@ (get-date) + foreach ($v in $vs.getset()) { + if ($v.value() -ne $null) { + $vsub = $v.value() + } else { + $vsub = '$null' + } + write-host -fore green "`t$($v.label()) = $($vsub)" + } +} + +function get-runduration( + [variableset] $vs + ) +{ + $vs.get('d') + $vs.get('Warm') + $vs.get('Cool') +} + +function get-doneflags( + [switch] $assertnone = $false + ) +{ + $assert = $false + + $tries = 0 + do { + + if ($tries -gt 0) { + sleep 1 + } + + # increment attempts + $tries += 1 + + # capture start of the iteration + $t0 = (get-date) + + # count number of done flags which agree with completion of the current go epoch + $good = 0 + dir $done |% { + $thisdone = gc $_ -ErrorAction SilentlyContinue + if ($thisdone -eq $goepoch) { + # if asserting that none should be complete, this would be an error! + if ($assertnone) { + write-host -fore red ERROR: $_.basename is already done + $assert = $true + } + $good += 1 + } + } + + # color status message per condition + if ($assert -or $good -ne $vms) { + $color = 'yellow' + } else { + $color = 'green' + } + + $t1 = (get-date) + $tdur = $t1 - $t0 + + write-host -fore $color done check iteration $tries with "$good/$vms" `@ $t1 "($('{0:F2}' -f $tdur.totalseconds) seconds)" + + # loop if not asserting, not all vms are done, and still have timeout to use + } while ((-not $assertnone) -and $good -ne $vms -and $tries -lt $timeout) + + # return assertion status? + if ($assertnone) { + return (-not $assert) + } + + # return incomplete run failure + if ($good -ne $vms) { + write-host -fore red ABORT: only received "$good/$vms" completions in $timeout attempts `@ (get-date) + return $false + } + + # all worked! + return $true +} + +function do-run( + [variableset] $vs + ) +{ + # apply specified run parameters. note null is ignored. + show-run $vs + + write-host -fore yellow Generating new runfile `@ (get-date) + new-runfile $vs + + # if we do not have a pause to clear, need the sleep here since go + # will release the fleet. + # smb fileinfo cache +5 seconds (so that fleet will see updated timestamp) + if (-not ($checkpause -and (check-pause -isactive:$true))) { + $script:checkpause = $false + sleep 15 + } + + write-host START Go Epoch: $goepoch `@ (get-date) + echo $goepoch > $go + + # release any active pause on first loop + if ($checkpause) { + write-host CLEAR PAUSE `@ (get-date) + + # same sleep need, pause will release the fleet + $script:checkpause = $false + sleep 15 + + # capture time zero prior to clear - can take time + $t0 = get-date + + clear-pause + } else { + + # capture time zero + $t0 = get-date + } + + # start performance counter capture + if ($pc -ne $null) { + $curpclabel = $vs.label($labeltemplate) + icm (get-clusternode) -ArgumentList (get-command start-logman) { + + param($fn) + set-item -path function:\$($fn.name) -value $fn.definition + + start-logman $env:COMPUTERNAME $using:curpclabel $using:pc + } + } + + ###### + + # sleep half, check for false done if possible (clear can take time/short runs), continue + $sleep = get-runduration $vs + + $t1 = get-date + $td = $t1 - $t0 + + if ($midcheck) { + + $remainingsleep = $sleep/2 - $td.TotalSeconds + if ($remainingsleep -gt 0) { + write-host SLEEP TO MID-RUN "($('{0:F2}' -f $remainingsleep) seconds)" `@ (get-date) + sleep $remainingsleep + } + + if ($td.TotalSeconds -lt ($sleep - 5)) { + write-host MID-RUN CHECK Go Epoch: $goepoch `@ (get-date) + # check for early completions, assert none are done yet + if (-not (get-doneflags -assertnone:$true)) { + return $false + } + write-host -fore green MID-RUN CHECK PASS Go Epoch: $goepoch `@ (get-date) + } + + # capture time and sleep for the remaining interval + $t1 = get-date + $td = $t1 - $t0 + + $remainingsleep = $sleep - $td.TotalSeconds + if ($remainingsleep -gt 0) { + write-host SLEEP TO END "($('{0:F2}' -f $remainingsleep) seconds)" `@ (get-date) + sleep $remainingsleep + } + + } else { + + sleep ($sleep - $td.TotalSeconds) + } + + ###### + + # stop performance counter capture + if ($pc -ne $null) { + icm (get-clusternode) -ArgumentList (get-command stop-logman) { + + param($fn) + set-item -path function:\$($fn.name) -value $fn.definition + + stop-logman $env:COMPUTERNAME $using:curpclabel "C:\ClusterStorage\collect\control\result" + } + } + + if (-not (get-doneflags)) { + return $false + } + + # advance go epoch + $script:goepoch += 1 + + return $true +} + +############# + +$vms = (get-clustergroup |? GroupType -eq VirtualMachine |? Name -like "vm-*" |? State -ne Offline).count + +# spec location of control files +$go = "c:\clusterstorage\collect\control\flag\go" +$done = "c:\clusterstorage\collect\control\flag\done-*" + +$timeout = 120 +$checkpause = $true + +# ensure we start a new go epoch +$goepoch = 0 +$gocontent = gc $go -ErrorAction SilentlyContinue +if ($gocontent -eq '0') { + $goepoch = 1 +} + +# construct the variable list describing the sweep + +############################ +############################ +## Modify from here down +############################ +############################ + +# add any additional sweep parameters here to match those specified on the command line +# ensure your sweep template script contains substitutable elements for each +# +# __somename__ +# +# bracketed by two underscore characters. Consider adding your new parameters to +# the label template so that result files are well-named and distinguishable. + +$v = @() +$v += [variable]::new('b',$b) +$v += [variable]::new('t',$t) +$v += [variable]::new('o',$o) +$v += [variable]::new('w',$w) +$v += [variable]::new('p',$p) +$v += [variable]::new('iops',$iops) +$v += [variable]::new('d',$d) +$v += [variable]::new('Warm',$warm) +$v += [variable]::new('Cool',$cool) +$v += [variable]::new('AddSpec',$addspec) + +$sweep = [variableset]::new($v) + +do { + + write-host -ForegroundColor Cyan '---' + + $r = do-run $sweep + + if (-not $r) { + return + } + +} while (-not $sweep.increment()) \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/start-vmfleet.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/start-vmfleet.ps1 new file mode 100644 index 0000000..7b033ab --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/start-vmfleet.ps1 @@ -0,0 +1,53 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +param( + [string[]] $group = "*", + [int] $number = 0 + ) + +icm (get-clusternode |? State -eq Up) { + + $n = $using:number + + # failed is an unclean offline tbd root causes (can usually be recovered) + + $using:group |% { + + # sorted list of vms by vm number + $vms = @(Get-ClusterGroup |? OwnerNode -eq $env:COMPUTERNAME |? GroupType -eq VirtualMachine |? Name -like "vm-$_-*" | sort -Property @{ Expression = { $null = $_.Name -match '-(\d+)$'; [int] $matches[1] }}) + + # start limited number, if specified, else all + $(if ($n -gt 0 -and $vms.Count -gt $n) { + $vms[0..($n - 1)] + } else { + $vms + }) |? { + $_.State -eq 'Offline' -or $_.State -eq 'Failed' + } | Start-ClusterGroup + } +} | ft -AutoSize \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/stop-vmfleet.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/stop-vmfleet.ps1 new file mode 100644 index 0000000..3aee22c --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/stop-vmfleet.ps1 @@ -0,0 +1,53 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +param( + [string[]] $group = @('*'), + [ValidateSet('Save','Shutdown','TurnOff')][string] $method = 'Shutdown' + ) + +icm (get-clusternode |? State -eq Up) -arg $group,$method { + param([string[]] $group, $method) + + $group |% { + + $g = Get-ClusterGroup |? GroupType -eq VirtualMachine |? OwnerNode -eq $env:COMPUTERNAME |? Name -like "vm-$_-*" |? State -ne 'Offline' + + if ($g) { + + # stop-clustergroup currently defaults to vm save + # use remoted stop-vm for the shutdown case + if ($method -eq 'Save') { + $g | Stop-ClusterGroup + } elseif ($method -eq 'TurnOff') { + Stop-VM -ComputerName $env:COMPUTERNAME -Name $g.Name -Force -TurnOff + } else { + Stop-VM -ComputerName $env:COMPUTERNAME -Name $g.Name -Force + } + } + } +} | ft -AutoSize diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/sweep-cputarget.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/sweep-cputarget.ps1 new file mode 100644 index 0000000..dbe795f --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/sweep-cputarget.ps1 @@ -0,0 +1,212 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +param( + $outfile = "result-cputarget.tsv", + $cputargets = $(throw "please specify a set of cpu percentage targets"), + $addspec = "" + ) + +$result = "C:\ClusterStorage\collect\control\result" + +# count the number of vms in the configuration +$vms = (Get-ClusterResource |? ResourceType -eq 'Virtual Machine' | measure).Count + +# target cpu utilization and qos to +/- given percentage +$cputargetwindow = 5 +$qoswindow = 5 + +# clean result file and set column headers +del -Force $outfile -ErrorAction SilentlyContinue +'WriteRatio','QOS','AVCPU','IOPS','AVRLat','AVWLat' -join "`t" > $outfile + +# make qos policy and reset +Get-StorageQosPolicy -Name SweepQos -ErrorAction SilentlyContinue | Remove-StorageQosPolicy -Confirm:$false +New-StorageQosPolicy -Name SweepQoS -MinimumIops $qos -MaximumIops $qos -PolicyType Dedicated +set-storageqos -policyname SweepQoS + +function is-within( + $value, + $target, + $percentage + ) +{ + ($value -ge ($target - ($target*($percentage/100))) -and + $value -le ($target + ($target*($percentage/100)))) +} + +function get-pc( + [string] $blg, + [int] $center, + [string] $ctr +) +{ + # get central n samples of a performance counter's sample + $pc = Import-Counter -Path $blg -Counter "\\*\$ctr" + + $t0 = ($pc.length - $center)/2 + $t1 = $t0 + $center - 1 + ($pc[$t0 .. $t1].CounterSamples.CookedValue | measure -Average).Average +} + +$pc = @('\Hyper-V Hypervisor Logical Processor(_Total)\% Total Run Time', + '\Processor Information(_Total)\% Processor Performance', + '\Cluster CSVFS(_Total)\reads/sec', + '\Cluster CSVFS(_Total)\avg. sec/read', + '\Cluster CSVFS(_Total)\writes/sec', + '\Cluster CSVFS(_Total)\avg. sec/write') + +# limit the number of attempts per sweep (mix) to 4 per targeted cpu util +$sweeplimit = ($cputargets.count * 4) + +foreach ($w in 0,10,30) { + + # track measured qos points, starting at given value + $h = @{} + $qosinitial = $qos = 400 + + foreach ($cputarget in $cputargets) { + + # move the qos window using previous run information + if ($cputarget -ne $cputargets[0]) { + $nextqos = [int](($cputarget*$iops/$avcpu)/$vms) + $qos = $nextqos + } + + write-host -ForegroundColor Cyan Starting outer loop with CPU target $cputarget and initial QoS $qos + + do { + + # failsafes + if ($h[$qos]) { write-host -ForegroundColor Red already measured $qos; break } + if ($h.Keys.Count -ge $sweeplimit) { write-host -ForegroundColor Red $sweeplimit tries giving up; break } + + Set-StorageQosPolicy -Name SweepQoS -MaximumIops $qos + write-host -fore Cyan Starting loop with QoS target $qos + + $curaddspec = "$($addspec)w$($w)qos$qos" + start-sweep.ps1 -addspec $curaddspec -b 4 -o 32 -t 1 -w $w -p r -d 60 -warm 15 -cool 15 -pc $pc + + # HACKHACK bounce collect + Get-ClusterSharedVolume |? { $_.SharedVolumeInfo.FriendlyVolumeName -match 'collect' } | Move-ClusterSharedVolume + sleep 1 + + # get average IOPS at DISKSPD + + $iops = $(dir $result\*.xml |% { + $x = [xml](gc $_) + ($x.Results.TimeSpan.Iops.Bucket | measure -Property Total -Average).Average + } | measure -Sum).Sum + + # get average cpu utilization for central 60 seconds of each node + # get average of all nodes + + $avcpu = $(dir $result\*.blg |% { + + $center = 60 + $trt = get-pc $_ $center '\Hyper-V Hypervisor Logical Processor(_Total)\% Total Run Time' + $ppc = get-pc $_ $center '\Processor Information(_Total)\% Processor Performance' + $trt*$ppc/100 + + + } | measure -Average).Average + + # get average latency for central 60 seconds, all nodes + # note we must aggregate the product of av latency and iops per node to get total + # latency, and then divide by total iops to get whole-cluster average. + + $csvrtotal = 0 + $csvwtotal = 0 + + ($avrlat,$avwlat) = $(dir $result\*.blg |% { + + $csvrlat = get-pc $_ $center '\Cluster CSVFS(_Total)\avg. sec/read' + $csvwlat = get-pc $_ $center '\Cluster CSVFS(_Total)\avg. sec/write' + $csvr = get-pc $_ $center '\Cluster CSVFS(_Total)\reads/sec' + $csvw = get-pc $_ $center '\Cluster CSVFS(_Total)\writes/sec' + + $csvrtotal += $csvr + + $csvwtotal += $csvw + + [pscustomobject] @{ 'avrtime' = $csvrlat*$csvr; 'avwtime' = $csvwlat*$csvw } + + } | measure -Sum -Property avrtime,avwtime).Sum + + $avrlat /= $csvrtotal + $avwlat /= $csvwtotal + + # capture results + $w,$qos,$avcpu,$iops,$avrlat,$avwlat -join "`t" >> $outfile + + # archive results + compress-archive -Path $(dir $result\* -Exclude *.zip) -DestinationPath $result\$curaddspec.zip + dir $result\* -Exclude *.zip | del + write-host Archived results to $result\$curaddspec.zip + + # stop within targetwindow% of cpu (+/- % of target) + if (is-within $avcpu $cputarget $cputargetwindow) { + write-host -ForegroundColor Green "Stopping in target window at $('{0:N2}' -f $avcpu) with QoS $qos" + break + } + + # assume cpu and qos have a linear relationship - extrapolate to target + # could do a direct linear fit of measurements so far + $nextqos = [int]($cputarget*$qos/$avcpu) + + # stop if next qos target is within qoswindow% of any previous measurement + $inwindow = $false + foreach ($previous in $h.keys) { + + if (is-within $nextqos $previous $qoswindow) { + $inwindow = $true + break + } + } + + if ($inwindow) { + write-host -ForegroundColor Yellow "Stopping in window of prior measurement at $('{0:N2}' -f $avcpu) with QoS $qos" + break + } + + # stop if next qos target is less than initial + if ($nextqos -lt $qosinitial) { + write-host -ForegroundColor Red "Stopping with underflow targeting $nextqos less than initial $qosinitial" + break + } + + write-host -ForegroundColor Cyan "Loop acheived $('{0:N2}' -f $avcpu) @ QoS $qos v. target $cputarget - next loop targeting QoS $nextqos" + + # record this datapoint as measured, move along to the next + $h[$qos] = 1 + $qos = $nextqos + + } while ($true) + } +} + +set-storageqos -policyname $null \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/test-clusterhealth.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/test-clusterhealth.ps1 new file mode 100644 index 0000000..4a11545 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/test-clusterhealth.ps1 @@ -0,0 +1,637 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +param( + [switch] $CleanOperationalIssues = $false + ) + +function new-namedblock( + [string] $name, + [scriptblock] $block, + [switch] $nullpass = $true, + [int] $mustbe = -1 + ) +{ + new-object psobject -Property @{ + 'Name' = $name; + 'Block' = $block; + 'NullPass' = $nullpass; + 'MustBe' = $mustbe + } +} + +function write-blocktitle( + [string[]] $s + ) +{ + write-host -fore cyan ('*'*20) $s +} + +function display-jobs() +{ + BEGIN { $j = @() } + PROCESS { $j += $_ } + END { + # consume job results + $null = $j | wait-job + $j | sort -Property Name |% { + write-blocktitle $_.Name,('({0:F1}s)' -f ($_.PsEndTime - $_.PsBeginTime).TotalSeconds) + $_ | receive-job + $_ | remove-job + } + } +} + +# script block containers for helper functions + +$evfns = { + + function get-fltevents( + [decimal] $timedeltams, + [string] $provider, + [string[]] $evid, + [scriptblock] $flt = { $true }, + [string] $source = $null + ) + { + # simple query vs. a provider for a single event within some timedelta of current (in ms) + # optional addition of a source provider name filter (for system log filtering) +$qstr = @" + + + + + +"@ + +$srcstr = @" +Provider[@Name='_SOURCE_'] and +"@ + + $events = ($evid |% { + "EventID=$_" + }) -join " or " + + $query = $qstr -replace '_MS_',$timedeltams -replace '_PROV_',$provider -replace '_EVENTS_',$events + if ($source) { + $query = $query -replace '_SOURCE_',($srcstr -replace '_SOURCE_',$source) + } else { + $query = $query -replace '_SOURCE_','' + } + + Get-WinEvent -FilterXml $query -ErrorAction SilentlyContinue | Where-Object -FilterScript $flt + } +} + +$fns = { + + function do-clustersymmetry( + [object] $gather, + [object[]] $filters, + [switch] $saygather = $false + ) + { + # deserialization note: the scriptblocks on the inputs are downconverted to strings + # so as a result we must reinstantiate + + # assert all nodes agree on object counts from the provided gather element + $data = icm (get-clusternode |? State -eq up) ([scriptblock]::create($gather.block)) + + if ($saygather) { + write-host -ForegroundColor yellow ('*'*15) $gather.name + } + + foreach ($f in $filters) { + + write-host -fore yellow ('*'*10) $f.name + $r = $data | where-object -FilterScript ([scriptblock]::create($f.block)) + + # group results by node + $nodeg = $r | group -property pscomputername -NoElement + + # regular symmetery + # if grouping by count (per node) yields more than one element, we know some nodes have different counts + # i.e., not all are 60: perhaps one is 58, etc. + # this is always failure. + if ($nodeg -ne $null -and ($nodeg | group -Property Count | measure).count -ne 1) { + write-host -ForegroundColor Red Fail + $nodeg | sort -Property Name,Count | ft -autosize + } else { + if ($nodeg -ne $null) { + # if no enforced count or count is correct, pass + if ($f.mustbe -lt 0 -or $f.mustbe -eq $nodeg[0].Count) { + write-host -ForegroundColor Green Pass with $nodeg[0].Count per node + } else { + # enforced count not correct + write-host -ForegroundColor Red Fail - required count of $f.mustbe not consistent on each node + $nodeg | sort -Property Name,Count | ft -autosize + } + } elseif ($f.nullpass) { + write-host -ForegroundColor Green Pass with none on any node + } else { + write-host -ForegroundColor Red Fail with none on any node + } + } + } + } +} + +# Detect RDMA its type (by manufacturer) so that, if needed, we can assert QoS/Cos for RoCE + +$netadapters = Get-NetAdapterRdma |? Enabled | Get-NetAdapter + +$rdma = $false +$roce = $false +$rocematch = 'Mellanox' + +if ($netadapters) { + write-host -fore green Detected RDMA adapters: will require RDMA + $rdma = $true + + # will need a tweak as additional non-Mellanox RoCE arrive (and/or if IB) + $qroce = $netadapters |? DriverProvider -match $rocematch + if ($qroce) { + $drvdesc = $qroce[0].DriverDescription + + write-host -fore green Detected $qroce[0].DriverProvider RDMA adapters: will require RoCE configuration + write-host -fore green Adapter Description: $drvdesc + $roce = $true + } +} + +### Basic Health Checks: serialized + +$j = @() + +$j += start-job -Name 'Basic Health Checks' { + + # nodes up + $cn = Get-ClusterNode + + if ($cn.count -eq $($cn |? State -eq Up).count) { + write-host -ForegroundColor Green All cluster nodes Up + } else { + write-host -ForegroundColor Red Following cluster nodes not Up: + $cn |? State -ne Up + } + + # node uptime + $o = icm ($cn |? State -eq Up) { + $w = gwmi win32_operatingsystem + $w.ConvertToDateTime($w.localdatetime) - $w.ConvertToDateTime($w.lastbootuptime) + } + + $reboots = $o |? TotalHours -lt 1 + + if ($reboots.length -and $reboots.length -ne $o.length) { + write-host -ForegroundColor Yellow WARNING: $reboots.length nodes have rebooted in the last hour. Ensure that + write-host -ForegroundColor Yellow `t no unexpected events are occuring in the cluster. + } + + write-host -ForegroundColor Green Cluster node uptime: + $o | sort PsComputerName | ft PsComputerName,@{ Label="Uptime"; Expression={"{0}d:{1:00}h:{2:00}m.{3:00}s" -f $_.Days,$_.Hours,$_.Minutes,$_.Seconds}} + + # subsystem check + $ss = Get-StorageSubSystem |? Model -eq 'Clustered Windows Storage' + + if (($ss | measure).count -ne 1) { + write-host -ForegroundColor Red Expected single clustered storage subsystem, found: + $ss | ft -autosize + return + } + + $ssuh = $ss |? HealthStatus -ne Healthy + + if ($ssuh) { + write-host -ForegroundColor Red WARNING: clustered storage subsystem is not healthy + $ssuh | ft -AutoSize + + write-host -ForegroundColor Red Output of Debug-StorageSubSystem follows + $ssuh | Debug-StorageSubSystem | fl + } else { + write-host -ForegroundColor Green Clustered storage subsystem Healthy + } + + # pool health + $p = $ss | Get-StoragePool |? IsPrimordial -ne $true |? HealthStatus -ne Healthy + + if ($p -eq $null) { + write-host -ForegroundColor Green All pools Healthy + } else { + write-host -ForegroundColor Red Following pools not Healthy: + $p | ft -autosize + } +} + +# vd health + +$j += start-job -name 'Virtual Disk Health' { + + $ss = Get-StorageSubSystem |? Model -eq 'Clustered Windows Storage' + + $vd = $ss | Get-VirtualDisk |? HealthStatus -ne Healthy + + if ($vd -eq $null) { + write-host -fore green All operational virtual disks Healthy + } else { + write-host -fore red Following virtual disks not Healthy: + $vd | ft -autosize + } +} + +# disk state + +$j += start-job -name 'Physical Disk Health' { + $pd = Get-StorageSubSystem |? Model -eq 'Clustered Windows Storage' | Get-PhysicalDisk + + $nonauto = $pd |? Usage -notmatch 'Journal|Auto-Select' + if ($nonauto) { + write-host -fore yellow WARNING: there are physical disks which are not auto-select/journal for usage. + write-host -fore yellow `t It is possible that while storage resilience has been restored from + write-host -fore yellow `t a failure, it is no longer evenly distributed between cluster nodes. + write-host -fore yellow `t Consider recovering before doing performance/operational work. + $nonauto | ft -autosize + } else { + write-host -fore green All physical disks are in normal auto-select or journal state + } +} + +# consolidated op issues - should logically split? + +$j += start-job -name 'Operational Issues and Storage Jobs' -ArgumentList $CleanOperationalIssues { + + param( $CleanOperationalIssues ) + + $ev = icm (get-clusternode) { + get-winevent -LogName Microsoft-Windows-Storage-Storport/Operational |? Id -eq 502 |% { + $ex = [xml]$_.ToXml() + $guid = ($ex.Event.EventData.Data |? Name -eq ClassDeviceGuid).'#text' + $_ | Add-Member -NotePropertyName DeviceGuid -NotePropertyValue $guid -PassThru + } + } + if ($ev) { + write-host -fore yellow WARNING: unresponsive device events have been logged by storport. + write-host -fore yellow `tThese may correspond to retired devices, and should be investigated. + $ev | ft -autosize PsComputerName,TimeCreated,Id,DeviceGuid,Message + + write-host -fore yellow Corresponding devices by Device GUID: + $d = Get-StorageSubSystem Cluster* | Get-StoragePool |? IsPrimordial -eq $false | Get-PhysicalDisk + $ev |% { + $d |? ObjectId -match "PD:$($_.DeviceGuid)" + } | ft -AutoSize + } + + # look for livekernelreport and/or bugcheck dumps + + $dmps = icm (get-clusternode |? State -eq Up) { + + $obj = @() + + $obj += dir $($env:windir + "\livekernelreports") + $obj += dir $($env:windir + "\minidump") -ErrorAction SilentlyContinue + $obj += dir $($env:windir + "\memory.dmp") -ErrorAction SilentlyContinue + + if ($using:CleanOperationalIssues -and $obj.count -gt 0) { + $obj | del -Force -Recurse -ErrorAction SilentlyContinue + } + + $obj + + } | sort -property PsParentPath,LastWriteTime,PsComputerName + + if ($dmps) { + if ($CleanOperationalIssues) { + write-host -fore red NOTE: the following failure reports were forcibly removed + } else { + write-host -fore yellow WARNING: there are failure reports that may require triage + } + + $dmps | ft -AutoSize + } + + # Storage Jobs + + $sj = get-storagejob + if ($sj |? JobState -ne Completed) { + write-host -ForegroundColor red WARNING: there are active storage jobs running. Investigate the root cause before continuing. + $sj | ft -autosize + } else { + write-host -fore green No storage rebuild or regeneration jobs are active + } +} + +# SMB Connectivity Error Check + +$fltblk = { + + param( $ev, $warncol, $warn ) + + $r = icm (get-clusternode |? State -eq Up) -ArgumentList @((get-command get-fltevents), $ev) { + + param($fn, $ev) + + set-item -path function:\$($fn.name) -value $fn.definition + + $flttcp = { + # report tcp (type 1) connectivity events + $x = [xml] $_.ToXml() + [int]($x.Event.EventData.Data |? Name -eq 'ConnectionType').'#text' -eq 1 + } + + $fltrdma = { + # report rdma (type 2) connectivity events + $x = [xml] $_.ToXml() + [int]($x.Event.EventData.Data |? Name -eq 'ConnectionType').'#text' -eq 2 + } + + $last5 = (1000*60*5) + $lasthour = (1000*60*60) + $lastday = (1000*60*60*24) + + new-object psobject -Property @{ + 'RDMA Last5Min' = (get-fltevents -flt $fltrdma -timedeltams $last5 -provider "Microsoft-Windows-SmbClient/Connectivity" -evid $ev).count; + 'RDMA LastHour' = (get-fltevents -flt $fltrdma -timedeltams $lasthour -provider "Microsoft-Windows-SmbClient/Connectivity" -evid $ev).count; + 'RDMA LastDay' = (get-fltevents -flt $fltrdma -timedeltams $lastday -provider "Microsoft-Windows-SmbClient/Connectivity" -evid $ev).count; + 'TCP Last5Min' = (get-fltevents -flt $flttcp -timedeltams $last5 -provider "Microsoft-Windows-SmbClient/Connectivity" -evid $ev).count; + 'TCP LastHour' = (get-fltevents -flt $flttcp -timedeltams $lasthour -provider "Microsoft-Windows-SmbClient/Connectivity" -evid $ev).count; + 'TCP LastDay' = (get-fltevents -flt $flttcp -timedeltams $lastday -provider "Microsoft-Windows-SmbClient/Connectivity" -evid $ev).count; + } + } + + $hdr = (($r[0] | gm -MemberType NoteProperty |? Definition -like 'int*').Name | sort) + $rdmafail = ($r |% { $row = $_; $hdr |? {$_ -like 'RDMA*' } |% { $row.$_ }} | measure -sum).sum -ne 0 + + if ($rdmafail) { + write-host -ForegroundColor $warncol $warn + } + + $r | sort -Property PsComputerName | ft -Property (@('PsComputerName') + $hdr) +} + +$w = @" +WARNING: the SMB Client is receiving RDMA disconnects. This is an error whose root" +`t cause may be PFC/CoS misconfiguration (RoCE) on hosts or switches, physical" +`t issues (ex: bad cable), switch or NIC firmware issues, and will lead to severely" +`t degraded performance. Additional triage is included in other tests." +"@ + +$j += start-job -name 'SMB Connectivity Error Check - Disconnect Failures' -ArgumentList 30804,([ConsoleColor]'Red'),$w -InitializationScript $evfns $fltblk + +$w = @" +WARNING: the SMB Client is receiving RDMA connect errors. This is an error whose root +`t cause may be actual lack of connectivity or fundamental problems with the RDMA +`t network fabric. Please inspect especially if in the Last5 bucket. +"@ + +$j += start-job -name 'SMB Connectivity Error Check - Connect Failures' -ArgumentList 30803,([ConsoleColor]'Yellow'),$w -InitializationScript $evfns $fltblk + +if ($roce) { + + $j += start-job -name 'RoCE: Mellanox Disable Check' -InitializationScript $evfns { + + $r = icm (get-clusternode |? State -eq Up) -ArgumentList (get-command get-fltevents) { + + param($fn) + + set-item -path function:\$($fn.name) -value $fn.definition + + $r = get-fltevents -timedeltams (1000*60*60*24*30) -provider 'System' -source 'mlx4eth63' -evid 35 + } + + if ($r) { + + write-host -ForegroundColor red WARNING: Mellanox indicates that RDMA has been disabled due to mis/non-configuration + write-host -ForegroundColor red `t of Priority Flow Control at some point in the past 30 days. Unless this has been recently + write-host -ForegroundColor red `t "corrected," RDMA may be down. + write-host -ForegroundColor red Most recent event "(System log)" follows + $r[0] | fl + } else { + write-host -ForegroundColor Green Pass + } + } + + $j += start-job -name 'RoCE: Mellanox Error Check' { + + $r = $null + $pc = $null + + switch ($using:drvdesc) { + "Mellanox ConnectX-3 Pro Ethernet Adapter" { + $pc = @{ + '\Mellanox Adapter Diagnostic Counters(_Total)\Responder Out-of-order Sequence Received' = 'Out Of Order'; + '\Mellanox Adapter Traffic Counters(_Total)\Packets Received Bad CRC Error' = 'Rec BadCRC'; + '\Mellanox Adapter Traffic Counters(_Total)\Packets Received Frame Length Error' = 'Rec FrmLenErr'; + '\Mellanox Adapter Traffic Counters(_Total)\Packets Received Symbol Error' = 'Rec SymlErr'; + '\Mellanox Adapter Traffic Counters(_Total)\Packets Received Discarded' = 'Rec Discard'; + '\Mellanox Adapter Traffic Counters(_Total)\Packets Outbound Discarded' = 'Outbnd Discard' + '\Mellanox Adapter Traffic Counters(_Total)\Packets Outbound Errors' = 'Outbnd Err'; + } + } + "Mellanox ConnectX-4 Adapter" { + $pc = @{ + '\Mellanox WinOF-2 Diagnostics(_Total)\Responder out of order sequence received' = 'Out Of Order'; + '\Mellanox WinOF-2 Port Traffic(_Total)\Packets Received Bad CRC Error' = 'Rec BadCRC'; + '\Mellanox WinOF-2 Port Traffic(_Total)\Packets Received Frame Length Error' = 'Rec FrmLenErr'; + '\Mellanox WinOF-2 Port Traffic(_Total)\Packets Received Symbol Error' = 'Rec SymlErr'; + '\Mellanox WinOF-2 Port Traffic(_Total)\Packets Received Discarded' = 'Rec Discard'; + '\Mellanox WinOF-2 Port Traffic(_Total)\Packets Outbound Discarded' = 'Outbnd Discard' + '\Mellanox WinOF-2 Port Traffic(_Total)\Packets Outbound Errors' = 'Outbnd Err'; + } + } + default { + write-host -ForegroundColor Red "Unknown adapter type: $($using:drvdesc)" + } + } + + # no counters, no results + + if ($pc -ne $null) { + + $r = icm (get-clusternode |? State -eq Up) -ArgumentList $pc { + + param($pc) + + $c = get-counter ($pc.Keys |% { $_ }) -ErrorAction SilentlyContinue + if ($c) { + + $o = new-object psobject -Property @{ 'Errors' = $false } + $c.CounterSamples | sort -Property Path |% { + if ($_.path -match '\\\\[^\\]+(\\.*$)') { + $o | Add-Member -NotePropertyName $pc[$matches[1]] -NotePropertyValue $_.CookedValue + if ($_.CookedValue -ne 0) { $o.Errors = $true } + } + } + $o + } + } + } + + if ($r.length -ne (get-clusternode |? State -eq Up).length) { + + write-host -ForegroundColor Yellow WARNING: retransmit statistics not available from all nodes. Ensure driver updates applied. + write-host -ForegroundColor Yellow `t $r.length nodes responded out of $((get-clusternode |? Up).length) + + } + + if ($r |? Errors) { + + write-host -ForegroundColor Red "WARNING: Any non-zero error counters may indicate physical or switch/NIC" + write-host -ForegroundColor Red "`t issues, likely leading to packet drops and retransmits, which will degrade" + write-host -ForegroundColor Red "`t performance. At high enough rates they can lead to SMB connection drops." + } else { + write-host -ForegroundColor Green "Pass - no errors detected" + } + + $hdr = @( 'PsComputerName' ) + $hdr += $($pc.Values |% { $_ }) + + $r | sort -property PsComputerName | ft -Property $hdr + } +} + +## Begin Symmetry Checks + +$totalf = new-namedblock 'Total' { $true } +$totalf_nonull = new-namedblock 'Total' { $true } -nullpass:$false + +### +$t = new-namedblock 'Clusport Device Symmetry Check' { gwmi -namespace root\wmi ClusportDeviceInformation } +$f = @($totalf) +$f += ,(new-namedblock 'Disk Type' { $_.DeviceType -eq 0} -nullpass:$false) +# temporarily remove hybrid check - the attribute is not synchronously updated +# and so may be (harmlessly) inaccurate for a period of time in early configuration +#$f += ,(new-namedblock 'Hybrid Media' { $_.DeviceAttribute -band 0x4}) +$f += ,(new-namedblock 'Solid/Non-Rotational Media' { $_.DeviceAttribute -band 0x8}) +$f += ,(new-namedblock 'Enclosure Type' { $_.DeviceType -eq 1} -nullpass:$false) +$f += ,(new-namedblock 'Virtual' { $_.DeviceAttribute -band 0x1} -nullpass:$true) + +$j += start-job -InitializationScript $fns -Name $t.name { do-clustersymmetry $using:t $using:f } + +### +$t = new-namedblock 'Physical Disk View Symmetry Check' { Get-StorageSubSystem |? Model -eq 'Clustered Windows Storage' | Get-PhysicalDisk } +$f = @($totalf) + +$j += start-job -InitializationScript $fns -Name $t.name { do-clustersymmetry $using:t $using:f } + +### +$t = new-namedblock 'Enclosure View Symmetry Check' { Get-StorageSubSystem |? Model -eq 'Clustered Windows Storage' | Get-StorageEnclosure } +$f = @($totalf) + +$j += start-job -InitializationScript $fns -Name $t.name { do-clustersymmetry $using:t $using:f } + +### +$t = new-namedblock 'SMB SBL Multichannel Symmetry Check' { Get-SmbMultichannelConnection -SmbInstance SBL } +$f = @($totalf) +$f += ,(new-namedblock 'RDMA Capable' { $_.ClientRdmaCapable -and $_.ServerRdmaCapable } -nullpass:$(-not $rdma)) +$f += ,(new-namedblock 'Selected & Non-Failed' { $_.Selected -and -not $_.Failed } -nullpass:$(-not $rdma)) + +$j += start-job -InitializationScript $fns -Name $t.name { do-clustersymmetry $using:t $using:f } + +### +$t = new-namedblock 'SMB CSV Multichannel Symmetry Check' { Get-SmbMultichannelConnection -SmbInstance CSV } +$f = @($totalf) +$f += ,(new-namedblock 'RDMA Capable' { $_.ClientRdmaCapable -and $_.ServerRdmaCapable } -nullpass:$(-not $rdma)) +$f += ,(new-namedblock 'Selected & Non-Failed' { $_.Selected -and -not $_.Failed } -nullpass:$(-not $rdma)) + +$j += start-job -InitializationScript $fns -Name $t.name { do-clustersymmetry $using:t $using:f } + +### +if ($rdma) { + + # rdma gives us an easy way of identifying a set of adapters to do this test with. + # it would be good to extend this more generally + + $t = @() + $t += new-namedblock 'RDMA Adapter IP Check' { Get-NetAdapterRdma |? Enabled | Get-NetAdapter |? HardwareInterface | Get-NetIPAddress -ErrorAction SilentlyContinue |? AddressState -eq 'Preferred' } + $t += new-namedblock 'RDMA Adapter (Virtual) IP Check' { Get-NetAdapterRdma |? Enabled | Get-NetAdapter |? { -not $_.HardwareInterface } | Get-NetIPAddress -ErrorAction SilentlyContinue |? AddressState -eq 'Preferred' } + $t += new-namedblock 'RDMA Adapter (Physical) IP Check' { Get-NetAdapterRdma |? Enabled | Get-NetAdapter |? HardwareInterface | Get-NetIPAddress -ErrorAction SilentlyContinue |? AddressState -eq 'Preferred' } + $f = @($totalf) + + $j += start-job -InitializationScript $fns -Name $t[0].name { + + $using:t |% { do-clustersymmetry $_ $using:f -saygather:$true } + } +} + +### +$t = new-namedblock 'RDMA Adapters Symmetry Check' { Get-NetAdapterRdma |? Enabled | Get-NetAdapter } +$f = @($totalf) +$f += ,(new-namedblock 'Operational' { $_.Speed -gt 0 } -nullpass:$(-not $rdma)) +$f += ,(new-namedblock 'Up' { $_.ifOperStatus -eq 'Up' } -nullpass:$(-not $rdma)) + +$j += start-job -InitializationScript $fns -Name $t.name { do-clustersymmetry $using:t $using:f } + +### +if ($roce) { + + # assert SMB Direct policy defined + $t = new-namedblock 'RoCE/QoS Configuration for SMB Direct' { Get-NetQosPolicy } + $f = @($totalf_nonull) + $f += ,(new-namedblock 'SMB Direct' { $_.NetDirectPort -eq 445 -and $_.PriorityValue -ne 0 } -nullpass:$false) + + $j += start-job -InitializationScript $fns -Name $t.name { do-clustersymmetry $using:t $using:f } + + # this is strictly insufficient, should ensure the enabled one is the same as that defined for SMB Direct + $t = new-namedblock 'RoCE/CoS Definitions' { Get-NetQosFlowControl } + $f = @($totalf_nonull) + $f += ,(new-namedblock 'Enabled' { $_.Enabled } -nullpass:$false) + $f += ,(new-namedblock 'Disabled' { -not $_.Enabled } -nullpass:$false) + + $j += start-job -InitializationScript $fns -Name $t.name { do-clustersymmetry $using:t $using:f } + + <# + # + $t = new-namedblock 'RoCE/CoS Applied to RoCE RNICs' { Get-NetAdapterRdma | get-netadapter -Physical -ErrorAction SilentlyContinue |? DriverProvider -match $using:rocematch } + $f = @($totalf_nonull) + $f += ,(new-namedblock 'Operational FlowControl Specs' { $_.OperationalFlowControl } -nullpass:$false) + $f += ,(new-namedblock 'Operational Port/Protocol Classification Specs' { $_.OperationalClassifications } -nullpass:$false) + $f += ,(new-namedblock 'Operational CoS Traffic Classes' { $_.OperationalTrafficClasses } -nullpass:$false) + + $j += start-job -InitializationScript $fns -Name $t.name { do-clustersymmetry $using:t $using:f } + #> +} + +### +if (Get-StorageFileServer |? SupportsContinuouslyAvailableFileShare) { + + $t = new-namedblock 'SMB Server CA FS Scope Net Interface Symmetry Check' { + + $fs = Get-StorageFileServer |? SupportsContinuouslyAvailableFileShare + if ($fs) { + Get-SmbServerNetworkInterface |? ScopeName -eq $fs.FriendlyName + } else { + $null + } + } + $f = @() + $f = ,(new-namedblock 'Rdma Capable' { $_.RdmaCapable } -nullpass:$(-not $rdma)) + + $j += start-job -InitializationScript $fns -Name $t.name { do-clustersymmetry $using:t $using:f } +} + +# consume job results +$j | display-jobs diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/update-csv.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/update-csv.ps1 new file mode 100644 index 0000000..1e670f0 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/update-csv.ps1 @@ -0,0 +1,132 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +param( + [switch]$disableintegrity = $false, + [switch]$renamecsvmounts = $false, + [switch]$movecsv = $true, + [switch]$movevm = $true, + [switch]$shiftcsv = $false +) + +$csv = get-clustersharedvolume + +# handle restore cases by mapping the csv to the friendly name of the volume +# don't rely on the csv name to contain this data + +$vh = @{} +Get-Volume |? FileSystem -eq CSVFS |% { $vh[$_.Path] = $_ } + +$csv |% { + $v = $vh[$_.SharedVolumeInfo.Partition.Name] + if ($v -ne $null) { + $_ | Add-Member -NotePropertyName VDName -NotePropertyValue $v.FileSystemLabel + } +} + +if ($disableintegrity) { + $csv |% { + dir -r $_.SharedVolumeInfo.FriendlyVolumeName | Set-FileIntegrity -Enable:$false -ErrorAction SilentlyContinue + } +} + +if ($renamecsvmounts) { + $csv |% { + if ($_.SharedVolumeInfo.FriendlyVolumeName -match 'Volume\d+$') { + ren $_.SharedVolumeInfo.FriendlyVolumeName $_.VDName + } + } +} + +function move-csv( + $rehome = $true, + $shift = $false + ) +{ + if ($shift) { + + write-host -fore Yellow Shifting CSV owners + + # rotation order (n0 -> n1, n1 -> n2, ... nn -> n0) + $nodes = (Get-ClusterNode |? State -eq Up | sort -Property Name).Name + $nh = @{} + foreach ($i in 1..($nodes.Length-1)) { + $nh[$nodes[$i-1]] = $nodes[$i] + } + $nh[$nodes[$nodes.Length-1]] = $nodes[0] + + Get-ClusterNode |% { + $node = $_.Name + $csv |? VDName -match "$node(?:-.+){0,1}" |% { + $_ | Move-ClusterSharedVolume $nh[$_.OwnerNode.Name] + } + } + + } elseif ($rehome) { + + # write-host -fore Yellow Re-homing CSVs + + # move all csvs named by node names back to their named node + get-clusternode |? State -eq Up |% { + $node = $_.Name + $csv |? VDName -match "$node(?:-.+){0,1}" |? OwnerNode -ne $node |% { $_ | Move-ClusterSharedVolume $node } + } + } +} + +if ($shiftcsv) { + # shift rotates all csvs node ownership by one node, in lexical order of + # current node owner name. this is useful for forcing out-of-position ops. + move-csv -shift:$true +} elseif ($movecsv) { + # move puts all csvs back on their home node + move-csv -rehome:$true +} + +if ($movevm) { + + icm (Get-ClusterNode |? State -eq Up) { + + Get-ClusterGroup |? GroupType -eq VirtualMachine |% { + + if ($_.Name -like "vm-*-$env:COMPUTERNAME-*") { + if ($env:COMPUTERNAME -ne $_.OwnerNode) { + write-host -ForegroundColor yellow moving $_.name $_.OwnerNode '->' $env:COMPUTERNAME + + # the default move type is live, but live does not degenerately handle offline vms yet + if ($_.State -eq 'Offline') { + Move-ClusterVirtualMachineRole -Name $_.Name -Node $env:COMPUTERNAME -MigrationType Quick + } else { + Move-ClusterVirtualMachineRole -Name $_.Name -Node $env:COMPUTERNAME + } + } else { + # write-host -ForegroundColor green $_.name is on $_.OwnerNode + } + } + } + } +} diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/wait-result.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/wait-result.ps1 new file mode 100644 index 0000000..0ddb4e2 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/wait-result.ps1 @@ -0,0 +1,36 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +while ($true) { + + $empty = dir C:\ClusterStorage\collect\control\result |? Length -eq 0 + if ($empty) { continue } + sleep 20 + break +} + +write-host -fore green Done diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/watch-cluster.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/watch-cluster.ps1 new file mode 100644 index 0000000..ea7c0fa --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/watch-cluster.ps1 @@ -0,0 +1,531 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +param( + $Cluster = ".", + $SampleInterval = 2, + [ValidateSet("CSV FS","SSB Cache","SBL","SBL Local","SBL Remote","SBL*","S2D BW","Hyper-V LCPU","SMB SRV","SMB Transport","*")] + [string[]] $Sets = "CSV FS", + $Log = $null +) + +if ($null -ne $log) { + del -Force $log -ErrorAction SilentlyContinue +} + +function write-log( + [string[]] $str + ) +{ + if ($null -ne $log) { + $str |% { + "$(get-date) $_" | Out-File -Append -FilePath $log -Width 9999 -Encoding ascii + } + } +} + +# display name +# ctr name +# display order +# format string +# scalar multiplier + +class CounterColumn { + + [string] $displayname + [string] $setname + [string[]] $ctrname + [int] $width + [string] $fmt + [decimal] $multiplier + [ValidateSet("Average","AverageAggregate","Sum")][string] $aggregate + [boolean] $divider + + CounterColumn( + [string] $displayname, + [string] $setname, + [string[]] $ctrname, + [int] $width, + [string] $fmt, + [decimal] $multiplier, + [string] $aggregate, + [boolean] $divider + ) + { + $this.displayname = $displayname + $this.setname = $setname + $this.ctrname = $ctrname + $this.width = $width + $this.fmt = $fmt + $this.multiplier = $multiplier + $this.aggregate = $aggregate + $this.divider = $divider + + if ($this.width -lt $this.displayname.length + 1) { + $this.width = $this.displayname.length + 1 + } + } +} + +class CounterColumnSet { + + [CounterColumn[]] $columns + [string[]] $counters + [string] $topfmt + [string] $linfmt + [string] $name + + [string] $totalline + [string[]] $nodelines + + CounterColumnSet($name) + { + $this.columns = $null + $this.name = $name + } + + [void] Add([CounterColumn] $c) + { + $this.columns += $c + } + + [void] Seal() + { + # assemble the top-line fmt and per-line fmt strings + # top-line is just strings/width + # per-line adds the (likely numeric) format specifier + $n = 1 + $this.topfmt = $this.linfmt = "{0,-16}" + foreach ($col in $this.columns) { + $str = '' + if ($col.divider) { + $str += '| ' + } + $str += "{$n,-$($col.width)" + $this.topfmt += "$str}" + $this.linfmt += "$($str):$($col.fmt)}" + + $n += 1 + } + + # assemble the list of counter instances that will be needed + # note that some may be internally aggregated (i.e., total = read + write) + # in cases where a counterset does not provide an explicit total + $this.counters = ($this.columns |% { + $s = $_.setname + $_.ctrname |% { "\$($s)(_Total)\$($_)" } + } | group -NoElement).Name + } + + [void] DisplayPre( + [hashtable] $samples, + [hashtable] $psamples + ) + { + # aggregate each column across all live sampled nodes + $this.totalline = $this.linfmt -f $( + "Total" + foreach ($col in $this.columns) { + + # average aggreate doesn't work across nodes if the base is not consistent + # for instance: cannot average latency safely, but can average cpu utilization + if ($col.aggregate -ne 'Average' -or $col.aggregate -eq 'AverageAggregate') { + $(foreach ($node in $psamples.keys) { + get-samples $psamples $node $col + }) | get-aggregate $col + } else { + $null + } + } + ) + + # individual nodes + # flags downed/non-responsive nodes by noting which are not + # present in the processed samples + $this.nodelines = foreach ($node in $samples.keys | sort) { + if ($psamples.ContainsKey($node)) { + $this.linfmt -f $( + $node + foreach ($col in $this.columns) { + $s = get-samples $psamples $node $col + if ($null -ne $s) { + $a = $s | get-aggregate $col + $a + } else { + "-" + } + } + ) + } else { + $this.topfmt -f $( + $node + 0..($this.columns.count - 1) |% { "-" } + ) + } + } + } + + [void] Display() + { + write-host ($this.topfmt -f (,$this.name + $this.columns.displayname)) + write-host -fore green $this.totalline + $this.nodelines |% { write-host $_ } + } +} + +function get-aggregate( + [CounterColumn] $col + ) +{ + BEGIN { + $n = 0 + $v = 0 + } + PROCESS { + $n += 1 + $v += $_ + } + END { + if ($n -gt 0) { + switch -wildcard ($col.aggregate) { + 'Sum' { + #write-host $col.displayname $col.multipler $v + $col.multiplier * $v + } + 'Average*' { + #write-host $col.displayname $col.multiplier $v $n + $col.multiplier * $v / $n + } + } + } else { + $null + } + } +} + +# get samples out of per-node hash of ctr hashes +function get-samples( + [hashtable] $h, + [string] $node, + [CounterColumn] $col + ) +{ + foreach ($i in $col.ctrname) { + + $k = "$($col.setname)+$($i)" + + if ($h.ContainsKey($node)) { + if ($h[$node].ContainsKey($k)) { + $h[$node][$k] + } else { + write-log "missing $node[$k] : $($h[$node].Keys.Count) total keys" + } + } else { + write-log "missing $node" + } + } +} + +$allctrs = @() + +### +$c = [CounterColumnSet]::new("CSV FS") +$c.Add([CounterColumn]::new("IOPS", "Cluster CSVFS", @("Reads/sec","Writes/sec"), 12, '#,#', 1, 'Sum', $false)) +$c.Add([CounterColumn]::new("Reads", "Cluster CSVFS", @("Reads/sec"), 12, '#,#', 1, 'Sum', $false)) +$c.Add([CounterColumn]::new("Writes", "Cluster CSVFS", @("Writes/sec"), 12, '#,#', 1, 'Sum', $false)) + +$c.Add([CounterColumn]::new("BW (MB/s)", "Cluster CSVFS", @("Read Bytes/sec","Write Bytes/sec"), 13, '#,#', 0.000001, 'Sum', $true)) +$c.Add([CounterColumn]::new("Read", "Cluster CSVFS", @("Read Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) +$c.Add([CounterColumn]::new("Write", "Cluster CSVFS", @("Write Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + +$c.Add([CounterColumn]::new("Read Lat (ms)", "Cluster CSVFS", @("Avg. sec/Read"), 15, '0.000', 1000, 'Average', $true)) +$c.Add([CounterColumn]::new("Write", "Cluster CSVFS", @("Avg. sec/Write"), 8, '0.000', 1000, 'Average', $false)) + +$c.Add([CounterColumn]::new("Read QAvg", "Cluster CSVFS", @("Avg. Read Queue Length"), 11, '0.000', 1, 'Average', $true)) +$c.Add([CounterColumn]::new("Write", "Cluster CSVFS", @("Avg. Write Queue Length"), 8, '0.000', 1, 'Average', $false)) + +$c.Seal() +$allctrs += $c + +### +$c = [CounterColumnSet]::new("SSB Cache") + +$c.Add([CounterColumn]::new("Hit/Sec", "Cluster Storage Hybrid Disks", @("Cache Hit Reads/Sec"), 12, '#,#', 1, 'Sum', $false)) +$c.Add([CounterColumn]::new("Miss/Sec", "Cluster Storage Hybrid Disks", @("Cache Miss Reads/Sec"), 12, '#,#', 1, 'Sum', $false)) +$c.Add([CounterColumn]::new("Remap/Sec" ,"Cluster Storage Cache Stores", @("Page ReMap/sec"), 12, '#,#', 1, 'Sum', $false)) + +$c.Add([CounterColumn]::new("Cache (MB/s)", "Cluster Storage Hybrid Disks", @("Cache Populate Bytes/sec","Cache Write Bytes/sec"), 13, '#,#', 0.000001, 'Sum', $true)) +$c.Add([CounterColumn]::new("RdPop", "Cluster Storage Hybrid Disks", @("Cache Populate Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) +$c.Add([CounterColumn]::new("WrPop", "Cluster Storage Hybrid Disks", @("Cache Write Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + +$c.Add([CounterColumn]::new("Destage (MB/s)", "Cluster Storage Cache Stores", @("Destage Bytes/sec"), 15, '#,#', 0.000001, 'Sum', $true)) +$c.Add([CounterColumn]::new("Update", "Cluster Storage Cache Stores", @("Update Bytes/sec"), 7, '#,#', 0.000001, 'Sum', $false)) + +$c.Add([CounterColumn]::new("Total (Pgs)", "Cluster Storage Cache Stores", @("Cache Pages"), 11, '0.00E+0', 1, 'Sum', $true)) +$c.Add([CounterColumn]::new("Standby", "Cluster Storage Cache Stores", @("Cache Pages StandBy"), 9, '0.00E+0', 1, 'Sum', $false)) +$c.Add([CounterColumn]::new("L0", "Cluster Storage Cache Stores", @("Cache Pages StandBy L0"), 9, '0.00E+0', 1, 'Sum', $false)) +$c.Add([CounterColumn]::new("L1", "Cluster Storage Cache Stores", @("Cache Pages StandBy L1"), 9, '0.00E+0', 1, 'Sum', $false)) +$c.Add([CounterColumn]::new("L2", "Cluster Storage Cache Stores", @("Cache Pages StandBy L2"), 9, '0.00E+0', 1, 'Sum', $false)) +$c.Add([CounterColumn]::new("Dirty", "Cluster Storage Cache Stores", @("Cache Pages Dirty"), 9, '0.00E+0', 1, 'Sum', $false)) + +$c.Seal() +$allctrs += $c + +### + +foreach ($subset in '','Local','Remote') { + $name = 'SBL' + $prefix = '' + if ($subset.Length) { + $name += " $subset" + $prefix = "$($subset): " + } + + $c = [CounterColumnSet]::new($name) + + $c.Add([CounterColumn]::new("IOPS", "Cluster Disk Counters", @(($prefix + "Read/sec"),($prefix + "Writes/sec")), 12, '#,#', 1, 'Sum', $false)) + $c.Add([CounterColumn]::new("Reads", "Cluster Disk Counters", @($prefix + "Read/sec"), 12, '#,#', 1, 'Sum', $false)) + $c.Add([CounterColumn]::new("Writes", "Cluster Disk Counters", @($prefix + "Writes/sec"), 12, '#,#', 1, 'Sum', $false)) + + $c.Add([CounterColumn]::new("BW (MB/s)", "Cluster Disk Counters", @(($prefix + "Read - Bytes/sec"),($prefix + "Write - Bytes/sec")), 13, '#,#', 0.000001, 'Sum', $true)) + $c.Add([CounterColumn]::new("Read", "Cluster Disk Counters", @($prefix + "Read - Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + $c.Add([CounterColumn]::new("Write", "Cluster Disk Counters", @($prefix + "Write - Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + + $c.Add([CounterColumn]::new("Read Lat (ms)", "Cluster Disk Counters", @($prefix + "Read Latency"), 15, '0.000', 1000, 'Average', $true)) + $c.Add([CounterColumn]::new("Write", "Cluster Disk Counters", @($prefix + "Write Latency"), 8, '0.000', 1000, 'Average', $false)) + + $c.Add([CounterColumn]::new("Read QAvg", "Cluster Disk Counters", @($prefix + "Read Avg. Queue Length"), 11, '0.000', 1, 'Average', $true)) + $c.Add([CounterColumn]::new("Write", "Cluster Disk Counters", @($prefix + "Write Avg. Queue Length"), 8, '0.000', 1, 'Average', $false)) + + $c.Seal() + $allctrs += $c +} + +### +$c = [CounterColumnSet]::new("SMB SRV") + +$c.Add([CounterColumn]::new("IOPS", "SMB Server Shares", @("Data Requests/sec"), 12, '#,#', 1, 'Sum', $false)) +$c.Add([CounterColumn]::new("Reads", "SMB Server Shares", @("Read Requests/sec"), 12, '#,#', 1, 'Sum', $false)) +$c.Add([CounterColumn]::new("Writes", "SMB Server Shares", @("Write Requests/sec"), 12, '#,#', 1, 'Sum', $false)) + +$c.Add([CounterColumn]::new("Data BW (MB/s)", "SMB Server Shares", @("Data Bytes/sec"), 13, '#,#', 0.000001, 'Sum', $true)) +$c.Add([CounterColumn]::new("Read", "SMB Server Shares", @("Read Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) +$c.Add([CounterColumn]::new("Write", "SMB Server Shares", @("Write Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + +$c.Add([CounterColumn]::new("Total BW (MB/s)", "SMB Server Shares", @("Transferred Bytes/sec"), 13, '#,#', 0.000001, 'Sum', $true)) +$c.Add([CounterColumn]::new("Rcv", "SMB Server Shares", @("Received Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) +$c.Add([CounterColumn]::new("Snd", "SMB Server Shares", @("Sent Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + +$c.Seal() +$allctrs += $c + +## +$c = [CounterColumnSet]::new("S2D BW") + +$c.Add([CounterColumn]::new("CSV (MB/s)", "Cluster CSVFS", @("Read Bytes/sec","Write Bytes/sec"), 10, '#,#', 0.000001, 'Sum', $false)) +$c.Add([CounterColumn]::new("Read", "Cluster CSVFS", @("Read Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) +$c.Add([CounterColumn]::new("Write", "Cluster CSVFS", @("Write Bytes/sec"), 8 ,'#,#', 0.000001, 'Sum', $false)) + +$c.Add([CounterColumn]::new("SBL (MB/s)", "Cluster Disk Counters", @("Read - Bytes/sec","Write - Bytes/sec"), 10, '#,#', 0.000001, 'Sum', $true)) +$c.Add([CounterColumn]::new("Read", "Cluster Disk Counters", @("Read - Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) +$c.Add([CounterColumn]::new("Write", "Cluster Disk Counters", @("Write - Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + +$c.Add([CounterColumn]::new("Cache (MB/s)", "Cluster Storage Hybrid Disks", @("Cache Hit Read Bytes/sec","Cache Write Bytes/sec"), 12, '#,#', 0.000001, 'Sum', $true)) +$c.Add([CounterColumn]::new("Read", "Cluster Storage Hybrid Disks", @("Cache Hit Read Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) +$c.Add([CounterColumn]::new("Write", "Cluster Storage Hybrid Disks", @("Cache Write Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + +$c.Add([CounterColumn]::new("Disk (MB/s)", "Cluster Storage Hybrid Disks", @("Disk Read Bytes/sec","Disk Write Bytes/sec"), 11, '#,#', 0.000001, 'Sum', $true)) +$c.Add([CounterColumn]::new("Read", "Cluster Storage Hybrid Disks", @("Disk Read Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) +$c.Add([CounterColumn]::new("Write", "Cluster Storage Hybrid Disks", @("Disk Write Bytes/sec"), 8, '#,#', 0.000001, 'Sum', $false)) + +$c.Seal() +$allctrs += $c + +## +$c = [CounterColumnSet]::new("Hyper-V LCPU") +$c.Add([CounterColumn]::new("Logical Total%", "Hyper-V Hypervisor Logical Processor", @("% Total Run Time"), 8, "0.00", 1, 'AverageAggregate', $false)) +$c.Add([CounterColumn]::new("Guest%", "Hyper-V Hypervisor Logical Processor", @("% Guest Run Time"), 8, "0.00", 1, 'AverageAggregate', $false)) +$c.Add([CounterColumn]::new("Hypervisor%", "Hyper-V Hypervisor Logical Processor", @("% Hypervisor Run Time"), 13, "0.00", 1, 'AverageAggregate', $false)) + +$c.Add([CounterColumn]::new("Root Total%", "Hyper-V Hypervisor Root Virtual Processor", @("% Total Run Time"), 12, "0.00", 1, 'AverageAggregate', $true)) +$c.Add([CounterColumn]::new("Guest%", "Hyper-V Hypervisor Root Virtual Processor", @("% Guest Run Time"), 8, "0.00", 1, 'AverageAggregate', $false)) +$c.Add([CounterColumn]::new("Hypervisor%", "Hyper-V Hypervisor Root Virtual Processor", @("% Hypervisor Run Time"), 12, "0.00", 1, 'AverageAggregate', $false)) +$c.Add([CounterColumn]::new("Remote%", "Hyper-V Hypervisor Root Virtual Processor", @("% Remote Run Time"), 7, "0.00", 1, 'AverageAggregate', $false)) + +$c.Seal() +$allctrs += $c + +## +$c = [CounterColumnSet]::new("SMB Transport") +$c.add([CounterColumn]::new("Read IOPS", "SMB Client Shares", @("Read Requests/sec"), 11, "#,#", 1, 'Sum', $false)) +$c.add([CounterColumn]::new("Write", "SMB Client Shares", @("Write Requests/sec"), 8, "#,#", 1, 'Sum', $false)) + +$c.add([CounterColumn]::new("RDMA Read", "SMB Client Shares", @("Read Requests transmitted via SMB Direct/sec"), 11, "#,#", 1, 'Sum', $true)) +$c.add([CounterColumn]::new("Write", "SMB Client Shares", @("Write Requests transmitted via SMB Direct/sec"), 8, "#,#", 1, 'Sum', $false)) + +$c.Seal() +$allctrs += $c + +## +if ($Sets.Count -eq 1 -and $Sets[0] -eq '*') { + $ctrs = $allctrs +} else { + $ctrs = $Sets |% { + $s = $_ + $allctrs |? { $_.name -like $s } # allows the SBL* wildcard + } +} + +function start-sample( + [CounterColumnSet[]] $ctrs, + [int] $SampleInterval + ) +{ + # clear any previous job instance + Get-Job -Name watch-cluster -ErrorAction SilentlyContinue | Stop-Job + Get-Job -Name watch-cluster -ErrorAction SilentlyContinue | Remove-Job + + # flatten list of counters and uniquify for the total counter set + # some display counter sets may repeat specific values (which is fine) + $counters = ($ctrs.counters |% { $_ |% { $_ }} | group -NoElement).Name + + icm -AsJob -JobName watch-cluster (Get-ClusterNode -Cluster $Cluster) { + + # extract countersamples, the object does not survive transfer between powershell sessions + # extract as a list, not as the individual counters + get-counter -Continuous -SampleInterval $using:SampleInterval $using:ctrs.counters |% { + ,$_.countersamples + } + } +} + +# start the first sample job and allow frame draw the first time through +$j = start-sample $ctrs $SampleInterval +$downtime = $null +$skipone = $false +$loops = 0 +$restart = $false + +# hash of most recent samples/node +$samples = @{} +Get-ClusterNode -Cluster $Cluster |% { $samples[$_.Name] = $null } + +while ($true) { + + if (-not $restart) { + Start-Sleep -Seconds $SampleInterval + + # sleep again if needed to prime the sample pipeline; + # there are no samples if we just restarted the sampling jobs + if ($skipone) { + $skipone = $false + continue + } + + # receive updates into the per-node hash + foreach ($child in $j.ChildJobs) { + $samples[$child.Location] = $child | receive-job -ErrorAction SilentlyContinue + } + + # null out downed nodes and remember first time we saw one drop out + $down = 0 + $j.ChildJobs |? State -ne Running |% { + $samples[$_.Location] = $null + $down += 1 + } + if ($down -and $null -eq $downtime) { + $downtime = get-date + } + + # if everything is down, we will attempt restart + if ($down -eq $j.ChildJobs.Count) { + $restart = $true + break + } + } + + # if explicit restart is required, or it has been 30 seconds with a downed node, restart the jobs to retry + if ($restart -or ($null -ne $downtime -and ((get-date)-$downtime).totalseconds -gt 30)) { + $j | stop-job + $j | remove-job + $j = start-sample $ctrs $SampleInterval + + # force gc to clear out accumulated job state quickly + [system.gc]::Collect() + + $downtime = $null + $skipone = $true + $restart = $false + continue + } + + # now process samples into per-node hashes of set/ctr containing lists of the + # cooked values acrosss the (possible) multiple instances + $psamples = @{} + foreach ($node in $samples.keys) { + + if ($samples[$node]) { + + $nsamples = @{} + + # flatten samples - if we are lagging, we'll have a list + # of consecutive (increasing by timestamp) samples + # we could try to be more efficient by dumping all but the + # final sample, but later ... + $samples[$node] |% { $_ } |% { + + ($setinst,$ctr) = $($_.path -split '\\')[3..4] + $set = ($setinst -split '\(')[0] + + $k = "$set+$ctr" + $nsamples[$k] = $_.cookedvalue + } + + $psamples[$node] = $nsamples + } + } + + # post-process the samples into the counterset, then clear and dump + $ctrs.DisplayPre($samples, $psamples) + Clear-Host + $drawsep = $false + $ctrs |% { + if ($drawsep) { + write-host -fore Green $('-'*20) + } + $drawsep = $true + $_.Display() + + } + + # restart the jobs every so many loops to prevent resource growth + $loops += 1 + if ($loops -gt 100) { + $loops = 0 + $restart = $true + } +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/watch-cpu.ps1 b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/watch-cpu.ps1 new file mode 100644 index 0000000..31928e2 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Frameworks/VMFleet1.0/watch-cpu.ps1 @@ -0,0 +1,210 @@ +<# +DISKSPD - VM Fleet + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +#> + +param( + $ComputerName = $env:COMPUTERNAME, + $SampleInterval = 2 + ) + +function div-to-width( + [int] $div + ) +{ + # 0 - 100 scale + # ex: 4 -> 100/4 = 25 buckets + 1 more for == 100 + 1+100/$div +} + +function center-pad( + [string] $s, + [int] $width + ) +{ + if ($width -le $s.length) { + $s + } else { + (' ' * (($width - $s.length)/2) + $s) + } +} + +function get-legend( + [int] $width, + [int] $div + ) +{ + # now produce the scale, a digit at a time in vertical orientation + # at each multiple of 10% which aligns with a measurement bucket. + # the width is the width of the measured values + # + # 0 5 1 + # 0 0 + # 0 + + $lines = @() + $lines += ,('-' * $width) + + foreach ($dig in 0..2) { + $o = foreach ($pos in 0..($width - 1)) { + + $val = $pos * $div + if ($val % 10 -eq 0) { + switch ($dig) { + 0 { if ($val -eq 100) { 1 } else { $val / 10 }} + 1 { if ($val -ne 0) { 0 } else { ' ' }} + 2 { if ($val -eq 100) { 0 } else { ' ' }} + } + } else { ' ' } + } + + $lines += ,($o -join '') + } + + # trailing comments (horizontal scale name) + 'Percent CPU Utilization' |% { + $lines += ,(center-pad $_ $width) + } + + $lines +} + +# these are the valid divisions, in units of percentage width. +# they must evenly divide 100% and either 10% or 20% for scale markings. +# determine which is the best fit based on window width. + +$div = 0 +foreach ($i in 1,2,4,5) { + if ((div-to-width $i) -le [console]::WindowWidth) { + $div = $i + break + } +} + +# if nothing fit, widen to 4% divisions + +if ($div -eq 0) { + $div = 4 +} + +$width = div-to-width $div + +# get the constant legend; use the remaining height for the vertical cpu core bars. +# note total height includes variable label line at bottom (instance + aggregagte) +$legend = get-legend $width $div +$clip = [console]::WindowHeight - $legend.Count - 1 + +# insist on a clip no lower than 10 + +if ($clip -lt 10) { + $clip = 10 +} + +# set window and buffer size simultaneously so we don't have extra scrollbars +cls +[console]::SetWindowSize($width,$clip + $legend.Count + 1) +[console]::BufferWidth = [console]::WindowWidth +[console]::BufferHeight = [console]::WindowHeight + +# scale divisions at x% +# this should evenly divide 100% +$m = [array]::CreateInstance([int],$width) + +# which processor counterset should we use? +# pi is only the root partition if hv is active +# hvlp is the host physical processors when hv is active +# via ctrs, hv is active iff hvlp is present and has multiple instances +$cset = get-counter -ComputerName $ComputerName -ListSet 'Hyper-V Hypervisor Logical Processor' -ErrorAction SilentlyContinue +if ($cs -ne $null -and $cs.CounterSetType -eq [Diagnostics.PerformanceCounterCategoryType]::MultiInstance) { + $cpuset = '\Hyper-V Hypervisor Logical Processor(*)\% Total Run Time' +} else { + $cpuset = '\Processor Information(*)\% Processor Time' +} + +# processor performance counter (turbo/speedstep) +$ppset = '\Processor Information(_Total)\% Processor Performance' + +while ($true) { + + # reset measurements & the lines to output + $lines = @() + foreach ($i in 0..($m.length - 1)) { + $m[$i] = 0 + } + + # avoid remoting for the local case + if ($ComputerName -eq $env:COMPUTERNAME) { + $samp = (get-counter -SampleInterval $SampleInterval -Counter $cpuset,$ppset).Countersamples + } else { + $samp = (get-counter -SampleInterval $SampleInterval -Counter $cpuset,$ppset -ComputerName $ComputerName).Countersamples + } + + # get all specific instances and count them into appropriate measurement bucket + $samp |% { + + if ($_.Path -like "*$ppset") { # scaling factor for total utility + $pperf = $_.CookedValue/100 + } elseif ($_.InstanceName -notlike '*_Total') { # per cpu: ignore total and per-numa total + $m[[math]::Floor($_.CookedValue/$div)] += 1 + } elseif ($_.InstanceName -eq '_Total') { # get total + $total = $_.CookedValue + } + } + + # work down the veritical altitude of each strip, starting at vclip + $altitude = $clip + do { + $lines += ,($($m |% { + + # if we are potentially clipped, handle + if ($altitude -eq $clip) { + + # clipped? + # unclipped but at clip? + # nothing, less than altitude + + if ($_ -gt $altitude) { 'x' } + elseif ($_ -eq $altitude) { '*' } + else { ' ' } + + } else { + # normal + # >=, output + # <, nothing + if ($_ -ge $altitude) { '*' } + else { ' ' } + } + }) -join '') + } while (--$altitude) + + cls + write-host -NoNewline ($lines + $legend -join "`n") + write-host -NoNewLine ("`n" + (center-pad ("{2} Total: {0:0.0}% Normalized: {1:0.0}%" -f $total,($total*$pperf),$ComputerName) $width)) + + # move the cursor to indicate average utilization + # column number is zero based, width is 1-based + [console]::SetCursorPosition([math]::Floor(($width - 1)*$total/100),[console]::CursorTop-$legend.Count) + +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/IORequestGenerator/IORequestGenerator.cpp b/CristalDiskMark/source/diskspd22/IORequestGenerator/IORequestGenerator.cpp new file mode 100644 index 0000000..735d912 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/IORequestGenerator/IORequestGenerator.cpp @@ -0,0 +1,2866 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +//FUTURE EXTENSION: make it compile with /W4 + +// Windows 7 +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0601 +#endif + +#include "common.h" +#include "IORequestGenerator.h" + +#include +#include +#include //DISK_GEOMETRY +#include +#include + +#include //WNODE_HEADER + +#include "etw.h" +#include +#include "ThroughputMeter.h" +#include "OverlappedQueue.h" + +// Flags for RtlFlushNonVolatileMemory +#ifndef FLUSH_NV_MEMORY_IN_FLAG_NO_DRAIN +#define FLUSH_NV_MEMORY_IN_FLAG_NO_DRAIN (0x00000001) +#endif + +/*****************************************************************************/ +// gets size of a dynamic volume, return zero on failure +// +UINT64 GetDynamicPartitionSize(HANDLE hFile) +{ + assert(NULL != hFile && INVALID_HANDLE_VALUE != hFile); + + UINT64 size = 0; + VOLUME_DISK_EXTENTS diskExt = {0}; + PVOLUME_DISK_EXTENTS pDiskExt = &diskExt; + DWORD bytesReturned; + + DWORD status = ERROR_SUCCESS; + BOOL rslt; + + OVERLAPPED ovlp = {0}; + ovlp.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (ovlp.hEvent == nullptr) + { + PrintError("ERROR: Failed to create event (error code: %u)\n", GetLastError()); + return 0; + } + + rslt = DeviceIoControl(hFile, + IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, + NULL, + 0, + pDiskExt, + sizeof(VOLUME_DISK_EXTENTS), + &bytesReturned, + &ovlp); + if (!rslt) { + status = GetLastError(); + if (status == ERROR_MORE_DATA) { + status = ERROR_SUCCESS; + + bytesReturned = sizeof(VOLUME_DISK_EXTENTS) + ((pDiskExt->NumberOfDiskExtents - 1) * sizeof(DISK_EXTENT)); + pDiskExt = (PVOLUME_DISK_EXTENTS)LocalAlloc(LPTR, bytesReturned); + + if (pDiskExt) + { + rslt = DeviceIoControl(hFile, + IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, + NULL, + 0, + pDiskExt, + bytesReturned, + &bytesReturned, + &ovlp); + if (!rslt) + { + status = GetLastError(); + if (status == ERROR_IO_PENDING) + { + if (WAIT_OBJECT_0 != WaitForSingleObject(ovlp.hEvent, INFINITE)) + { + status = GetLastError(); + PrintError("ERROR: Failed while waiting for event to be signaled (error code: %u)\n", status); + } + else + { + status = ERROR_SUCCESS; + assert(pDiskExt->NumberOfDiskExtents <= 1); + } + } + else + { + PrintError("ERROR: Could not obtain dynamic volume extents (error code: %u)\n", status); + } + } + } + else + { + status = GetLastError(); + PrintError("ERROR: Could not allocate memory (error code: %u)\n", status); + } + } + else if (status == ERROR_IO_PENDING) + { + if (WAIT_OBJECT_0 != WaitForSingleObject(ovlp.hEvent, INFINITE)) + { + status = GetLastError(); + PrintError("ERROR: Failed while waiting for event to be signaled (error code: %u)\n", status); + } + else + { + status = ERROR_SUCCESS; + assert(pDiskExt->NumberOfDiskExtents <= 1); + } + } + else + { + PrintError("ERROR: Could not obtain dynamic volume extents (error code: %u)\n", status); + } + } + else + { + assert(pDiskExt->NumberOfDiskExtents <= 1); + } + + if (status == ERROR_SUCCESS) + { + for (DWORD n = 0; n < pDiskExt->NumberOfDiskExtents; n++) { + size += pDiskExt->Extents[n].ExtentLength.QuadPart; + } + } + + if (pDiskExt && (pDiskExt != &diskExt)) { + LocalFree(pDiskExt); + } + CloseHandle(ovlp.hEvent); + + return size; +} + +/*****************************************************************************/ +// gets partition size, return zero on failure +// +UINT64 GetPartitionSize(HANDLE hFile) +{ + assert(NULL != hFile && INVALID_HANDLE_VALUE != hFile); + + PARTITION_INFORMATION_EX pinf; + OVERLAPPED ovlp = {}; + + ovlp.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (ovlp.hEvent == nullptr) + { + PrintError("ERROR: Failed to create event (error code: %u)\n", GetLastError()); + return 0; + } + + DWORD rbcnt = 0; + DWORD status = ERROR_SUCCESS; + UINT64 size = 0; + + if (!DeviceIoControl(hFile, + IOCTL_DISK_GET_PARTITION_INFO_EX, + NULL, + 0, + &pinf, + sizeof(pinf), + &rbcnt, + &ovlp) + ) + { + status = GetLastError(); + if (status == ERROR_IO_PENDING) + { + if (WAIT_OBJECT_0 != WaitForSingleObject(ovlp.hEvent, INFINITE)) + { + PrintError("ERROR: Failed while waiting for event to be signaled (error code: %u)\n", GetLastError()); + } + else + { + size = pinf.PartitionLength.QuadPart; + } + } + else + { + size = GetDynamicPartitionSize(hFile); + } + } + else + { + size = pinf.PartitionLength.QuadPart; + } + + CloseHandle(ovlp.hEvent); + + return size; +} + +/*****************************************************************************/ +// gets physical drive size, return zero on failure +// +UINT64 GetPhysicalDriveSize(HANDLE hFile) +{ + assert(NULL != hFile && INVALID_HANDLE_VALUE != hFile); + + DISK_GEOMETRY_EX geom; + OVERLAPPED ovlp = {}; + + ovlp.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (ovlp.hEvent == nullptr) + { + PrintError("ERROR: Failed to create event (error code: %u)\n", GetLastError()); + return 0; + } + + DWORD rbcnt = 0; + DWORD status = ERROR_SUCCESS; + BOOL rslt; + + rslt = DeviceIoControl(hFile, + IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, + NULL, + 0, + &geom, + sizeof(geom), + &rbcnt, + &ovlp); + + if (!rslt) + { + status = GetLastError(); + if (status == ERROR_IO_PENDING) + { + if (WAIT_OBJECT_0 != WaitForSingleObject(ovlp.hEvent, INFINITE)) + { + PrintError("ERROR: Failed while waiting for event to be signaled (error code: %u)\n", GetLastError()); + } + else + { + rslt = TRUE; + } + } + else + { + PrintError("ERROR: Could not obtain drive geometry (error code: %u)\n", status); + } + } + + CloseHandle(ovlp.hEvent); + + if (!rslt) + { + return 0; + } + + return (UINT64)geom.DiskSize.QuadPart; +} + +/*****************************************************************************/ +// activates specified privilege in process token +// +bool SetPrivilege(LPCSTR pszPrivilege, LPCSTR pszErrorPrefix = "ERROR:") +{ + TOKEN_PRIVILEGES TokenPriv; + HANDLE hToken = INVALID_HANDLE_VALUE; + DWORD dwError; + bool fOk = true; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) + { + PrintError("%s Error opening process token (error code: %u)\n", pszErrorPrefix, GetLastError()); + fOk = false; + goto cleanup; + } + + TokenPriv.PrivilegeCount = 1; + TokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + if (!LookupPrivilegeValue(nullptr, pszPrivilege, &TokenPriv.Privileges[0].Luid)) + { + PrintError("%s Error looking up privilege value %s (error code: %u)\n", pszErrorPrefix, pszPrivilege, GetLastError()); + fOk = false; + goto cleanup; + } + + if (!AdjustTokenPrivileges(hToken, FALSE, &TokenPriv, 0, nullptr, nullptr)) + { + PrintError("%s Error adjusting token privileges for %s (error code: %u)\n", pszErrorPrefix, pszPrivilege, GetLastError()); + fOk = false; + goto cleanup; + } + + if (ERROR_SUCCESS != (dwError = GetLastError())) + { + PrintError("%s Error adjusting token privileges for %s (error code: %u)\n", pszErrorPrefix, pszPrivilege, dwError); + fOk = false; + goto cleanup; + } + +cleanup: + if (hToken != INVALID_HANDLE_VALUE) + { + CloseHandle(hToken); + } + + return fOk; +} + +BOOL +DisableLocalCache( + HANDLE h +) +/*++ +Routine Description: + + Disables local caching of I/O to a file by SMB. All reads/writes will flow to the server. + +Arguments: + + h - Handle to the file + +Return Value: + + Returns ERROR_SUCCESS (0) on success, nonzero error code on failure. + +--*/ +{ + DWORD BytesReturned = 0; + OVERLAPPED Overlapped = { 0 }; + DWORD Status = ERROR_SUCCESS; + BOOL Success = false; + + Overlapped.hEvent = CreateEvent(nullptr, true, false, nullptr); + if (!Overlapped.hEvent) + { + return GetLastError(); + } + +#ifndef FSCTL_DISABLE_LOCAL_BUFFERING +#define FSCTL_DISABLE_LOCAL_BUFFERING CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 174, METHOD_BUFFERED, FILE_ANY_ACCESS) +#endif + + Success = DeviceIoControl(h, + FSCTL_DISABLE_LOCAL_BUFFERING, + nullptr, + 0, + nullptr, + 0, + nullptr, + &Overlapped); + + if (!Success) { + Status = GetLastError(); + } + + if (!Success && Status == ERROR_IO_PENDING) + { + if (!GetOverlappedResult(h, &Overlapped, &BytesReturned, true)) + { + Status = GetLastError(); + } + else + { + Status = (DWORD) Overlapped.Internal; + } + } + + if (Overlapped.hEvent) + { + CloseHandle(Overlapped.hEvent); + } + + return Status; +} + +/*****************************************************************************/ +// structures and global variables +// +struct ETWEventCounters g_EtwEventCounters; + +__declspec(align(4)) static LONG volatile g_lRunningThreadsCount = 0; //must be aligned on a 32-bit boundary, otherwise InterlockedIncrement + //and InterlockedDecrement will fail on 64-bit systems + +static BOOL volatile g_bRun; //used for letting threads know that they should stop working + +typedef NTSTATUS (__stdcall *NtQuerySysInfo)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG); +static NtQuerySysInfo g_pfnNtQuerySysInfo; + +typedef VOID (__stdcall *RtlCopyMemNonTemporal)(VOID UNALIGNED *, VOID UNALIGNED *, SIZE_T); +static RtlCopyMemNonTemporal g_pfnRtlCopyMemoryNonTemporal; + +typedef NTSTATUS (__stdcall *RtlFlushNvMemory)(PVOID, PVOID, SIZE_T, ULONG); +static RtlFlushNvMemory g_pfnRtlFlushNonVolatileMemory; + +typedef NTSTATUS(__stdcall *RtlGetNvToken)(PVOID, SIZE_T, PVOID *); +static RtlGetNvToken g_pfnRtlGetNonVolatileToken; + +typedef NTSTATUS(__stdcall *RtlFreeNvToken)(PVOID); +static RtlFreeNvToken g_pfnRtlFreeNonVolatileToken; + +static BOOL volatile g_bThreadError = FALSE; //true means that an error has occured in one of the threads +BOOL volatile g_bTracing = TRUE; //true means that ETW is turned on + +// TODO: is this still needed? +__declspec(align(4)) static LONG volatile g_lGeneratorRunning = 0; //used to detect if GenerateRequests is already running + +static BOOL volatile g_bError = FALSE; //true means there was fatal error during intialization and threads shouldn't perform their work + +VOID SetProcGroupMask(WORD wGroupNum, ULONG dwProcNum, PGROUP_AFFINITY pGroupAffinity) +{ + //must zero this structure first, otherwise it fails to set affinity + memset(pGroupAffinity, 0, sizeof(GROUP_AFFINITY)); + + pGroupAffinity->Group = wGroupNum; + pGroupAffinity->Mask = (KAFFINITY)1<Group = wGroupNum; + pGroupAffinity->Mask = Mask; +} + +/*****************************************************************************/ +void IORequestGenerator::_CloseOpenFiles(vector& vhFiles) const +{ + for (size_t x = 0; x < vhFiles.size(); ++x) + { + if ((INVALID_HANDLE_VALUE != vhFiles[x]) && (nullptr != vhFiles[x])) + { + if (!CloseHandle(vhFiles[x])) + { + PrintError("Warning: unable to close file handle (error code: %u)\n", GetLastError()); + } + vhFiles[x] = nullptr; + } + } +} + +/*****************************************************************************/ +// wrapper for stderr +void PrintError(const char *format, ...) +{ + assert(NULL != format); + + va_list listArg; + va_start(listArg, format); + vfprintf(stderr, format, listArg); + va_end(listArg); +} + +/*****************************************************************************/ +// prints the string only if verbose mode is set to true +// +static void PrintVerbose(bool fVerbose, const char *format, ...) +{ + assert(NULL != format); + + if(fVerbose ) + { + SYSTEMTIME now; + char szBuffer[64]; // enough for timestamp+null + int nWritten; + + GetLocalTime(&now); + + if (now.wYear) { + + // Mimic .NET 's' sortable time pattern + nWritten = sprintf_s(szBuffer, _countof(szBuffer), + "%u-%02u-%02uT%02u:%02u:%02u", + now.wYear, + now.wMonth, + now.wDay, + now.wHour, + now.wMinute, + now.wSecond); + assert(nWritten && nWritten < _countof(szBuffer)); + + // no newline + printf("%s: " ,szBuffer); + } + + va_list argList; + va_start(argList, format); + vprintf(format, argList); + va_end(argList); + } +} + +/*****************************************************************************/ +// thread for gathering ETW data (etw functions are defined in etw.cpp) +// +DWORD WINAPI etwThreadFunc(LPVOID cookie) +{ + UNREFERENCED_PARAMETER(cookie); + + g_bTracing = TRUE; + BOOL result = TraceEvents(); + g_bTracing = FALSE; + + return result ? 0 : 1; +} + +/*****************************************************************************/ +bool IORequestGenerator::_LoadDLLs() +{ + _hNTDLL = LoadLibraryExW(L"ntdll.dll", nullptr, 0); + if( nullptr == _hNTDLL ) + { + return false; + } + + g_pfnNtQuerySysInfo = (NtQuerySysInfo)GetProcAddress(_hNTDLL, "NtQuerySystemInformation"); + if( nullptr == g_pfnNtQuerySysInfo ) + { + return false; + } + + g_pfnRtlCopyMemoryNonTemporal = (RtlCopyMemNonTemporal)GetProcAddress(_hNTDLL, "RtlCopyMemoryNonTemporal"); + g_pfnRtlFlushNonVolatileMemory = (RtlFlushNvMemory)GetProcAddress(_hNTDLL, "RtlFlushNonVolatileMemory"); + g_pfnRtlGetNonVolatileToken = (RtlGetNvToken)GetProcAddress(_hNTDLL, "RtlGetNonVolatileToken"); + g_pfnRtlFreeNonVolatileToken = (RtlFreeNvToken)GetProcAddress(_hNTDLL, "RtlFreeNonVolatileToken"); + + return true; +} + +/*****************************************************************************/ +bool IORequestGenerator::_GetSystemPerfInfo(vector& vSPPI, bool fVerbose) const +{ + NTSTATUS Status; + ULONG CpuBase; + WORD Group; + WORD GroupCount; + GROUP_AFFINITY GroupAffinity; + + for (CpuBase = 0, Group = 0, GroupCount = (WORD) g_SystemInformation.processorTopology._vProcessorGroupInformation.size(); + Group < GroupCount; + Group++) + { + ProcessorGroupInformation *pGroup = &g_SystemInformation.processorTopology._vProcessorGroupInformation[Group]; + + // + // Note that an inactive group is not queried (its not clear this is a practical case). + // Correct operation assumes the input SPPI array is prezeroed, which DISKSPD does do via + // default vector(size_t) construction. + // + + if (pGroup->_activeProcessorCount != 0) + { + // + // In multigroup environments, affinitize to the group we're querying counters from. + // + + if (GroupCount > 1) + { + SetGroupMask(Group, pGroup->_activeProcessorMask, &GroupAffinity); + if (!SetThreadGroupAffinity(GetCurrentThread(), &GroupAffinity, nullptr)) + { + PrintError("get system perf info: failed to set affinity to Group %u\n", GroupAffinity.Group); + return false; + } + } + + // + // The SPPI vector should (is) always be sized to span CPUs for all groups, make this explicit. + // + + if (CpuBase + pGroup->_activeProcessorCount > vSPPI.size()) + { + PrintError("get system perf info: unable to return (base CPU %u + group active CPU %u > size %u)\n", + CpuBase, + pGroup->_activeProcessorCount, + vSPPI.size()); + assert(false); + return false; + } + + Status = g_pfnNtQuerySysInfo(SystemProcessorPerformanceInformation, + &vSPPI[CpuBase], + sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION) * pGroup->_activeProcessorCount, + nullptr); + + if (!NT_SUCCESS(Status)) + { + PrintError("get system perf info: status 0x%x querying for Group %u (%u CPUs)\n", + Status, + Group, + pGroup->_activeProcessorCount); + return false; + } + + PrintVerbose(fVerbose, + "get system perf info: queried for Group %u (%u CPUs)\n", + Group, + pGroup->_activeProcessorCount); + } + + CpuBase += pGroup->_activeProcessorCount; + } + + return true; +} + +VOID CALLBACK fileIOCompletionRoutine(DWORD dwErrorCode, DWORD dwBytesTransferred, LPOVERLAPPED pOverlapped); + +static bool issueNextIO(ThreadParameters *p, IORequest *pIORequest, DWORD *pdwBytesTransferred, bool useCompletionRoutines) +{ + OVERLAPPED *pOverlapped = pIORequest->GetOverlapped(); + Target *pTarget = pIORequest->GetCurrentTarget(); + size_t iTarget = pIORequest->GetCurrentTargetIndex(); + UINT32 iRequest = pIORequest->GetRequestIndex(); + LARGE_INTEGER li; + BOOL rslt = true; + + // + // Compute next IO + // + + p->vTargetStates[iTarget].NextIORequest(*pIORequest); + + li.LowPart = pIORequest->GetOverlapped()->Offset; + li.HighPart = pIORequest->GetOverlapped()->OffsetHigh; + + if (TraceLoggingProviderEnabled(g_hEtwProvider, + TRACE_LEVEL_VERBOSE, + DISKSPD_TRACE_IO)) + { + GUID ActivityId = p->NextActivityId(); + pIORequest->SetActivityId(ActivityId); + + TraceLoggingWriteActivity(g_hEtwProvider, + "DiskSpd IO", + &ActivityId, + NULL, + TraceLoggingKeyword(DISKSPD_TRACE_IO), + TraceLoggingOpcode(EVENT_TRACE_TYPE_START), + TraceLoggingLevel(TRACE_LEVEL_VERBOSE), + TraceLoggingUInt32(p->ulThreadNo, "Thread"), + TraceLoggingString(pIORequest->GetIoType() == IOOperation::ReadIO ? "Read" : "Write", "IO Type"), + TraceLoggingUInt64(iTarget, "Target"), + TraceLoggingInt32(pTarget->GetBlockSizeInBytes(), "Block Size"), + TraceLoggingInt64(li.QuadPart, "Offset")); + } + +#if 0 + PrintError("t[%u:%u] issuing %u %s @ %I64u)\n", p->ulThreadNo, iTarget, + pTarget->GetBlockSizeInBytes(), + (pIORequest->GetIoType() == IOOperation::ReadIO ? "read" : "write"), + li.QuadPart); +#endif + + if (p->pTimeSpan->GetMeasureLatency() || p->pTimeSpan->GetCalculateIopsStdDev()) + { + pIORequest->SetStartTime(PerfTimer::GetTime()); + } + + if (pIORequest->GetIoType() == IOOperation::ReadIO) + { + if (pTarget->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) + { + if (pTarget->GetWriteThroughMode() == WriteThroughMode::On ) + { + g_pfnRtlCopyMemoryNonTemporal(p->GetReadBuffer(iTarget, iRequest), pTarget->GetMappedView() + li.QuadPart, pTarget->GetBlockSizeInBytes()); + } + else + { + memcpy(p->GetReadBuffer(iTarget, iRequest), pTarget->GetMappedView() + li.QuadPart, pTarget->GetBlockSizeInBytes()); + } + *pdwBytesTransferred = pTarget->GetBlockSizeInBytes(); + } + else + { + if (useCompletionRoutines) + { + rslt = ReadFileEx(p->vhTargets[iTarget], p->GetReadBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes(), pOverlapped, fileIOCompletionRoutine); + } + else + { + rslt = ReadFile(p->vhTargets[iTarget], p->GetReadBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes(), pdwBytesTransferred, pOverlapped); + } + } + } + else + { + if (pTarget->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) + { + if (pTarget->GetWriteThroughMode() == WriteThroughMode::On) + { + g_pfnRtlCopyMemoryNonTemporal(pTarget->GetMappedView() + li.QuadPart, p->GetWriteBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes()); + } + else + { + memcpy(pTarget->GetMappedView() + li.QuadPart, p->GetWriteBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes()); + + switch (pTarget->GetMemoryMappedIoFlushMode()) + { + case MemoryMappedIoFlushMode::ViewOfFile: + FlushViewOfFile(pTarget->GetMappedView() + li.QuadPart, pTarget->GetBlockSizeInBytes()); + break; + case MemoryMappedIoFlushMode::NonVolatileMemory: + g_pfnRtlFlushNonVolatileMemory(pTarget->GetMemoryMappedIoNvToken(), pTarget->GetMappedView() + li.QuadPart, pTarget->GetBlockSizeInBytes(), 0); + break; + case MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain: + g_pfnRtlFlushNonVolatileMemory(pTarget->GetMemoryMappedIoNvToken(), pTarget->GetMappedView() + li.QuadPart, pTarget->GetBlockSizeInBytes(), FLUSH_NV_MEMORY_IN_FLAG_NO_DRAIN); + break; + } + } + *pdwBytesTransferred = pTarget->GetBlockSizeInBytes(); + } + else + { + if (useCompletionRoutines) + { + rslt = WriteFileEx(p->vhTargets[iTarget], p->GetWriteBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes(), pOverlapped, fileIOCompletionRoutine); + } + else + { + rslt = WriteFile(p->vhTargets[iTarget], p->GetWriteBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes(), pdwBytesTransferred, pOverlapped); + } + } + } + + if (p->vThroughputMeters.size() != 0 && p->vThroughputMeters[iTarget].IsRunning()) + { + p->vThroughputMeters[iTarget].Adjust(pTarget->GetBlockSizeInBytes()); + } + + return (rslt) ? true : false; +} + + +void completeIOat(ThreadParameters *p, IORequest *pIORequest, DWORD dwBytesTransferred, UINT64 ullCompletionTime) +{ + if (*p->pfAccountingOn) + { + p->pResults->vTargetResults[pIORequest->GetCurrentTargetIndex()].Add( + dwBytesTransferred, + pIORequest->GetIoType(), + pIORequest->GetStartTime(), + ullCompletionTime, + *(p->pullStartTime), + p->pTimeSpan->GetMeasureLatency(), + p->pTimeSpan->GetCalculateIopsStdDev()); + } + + if (TraceLoggingProviderEnabled(g_hEtwProvider, + TRACE_LEVEL_VERBOSE, + DISKSPD_TRACE_IO)) + { + GUID ActivityId = pIORequest->GetActivityId(); + + TraceLoggingWriteActivity(g_hEtwProvider, + "DiskSpd IO", + &ActivityId, + NULL, + TraceLoggingKeyword(DISKSPD_TRACE_IO), + TraceLoggingOpcode(EVENT_TRACE_TYPE_STOP), + TraceLoggingLevel(TRACE_LEVEL_VERBOSE)); + } + + Target *pTarget = pIORequest->GetCurrentTarget(); + + //check if I/O transferred all of the requested bytes + if (dwBytesTransferred != pTarget->GetBlockSizeInBytes()) + { + PrintError("Warning: thread %u transferred %u bytes instead of %u bytes\n", + p->ulThreadNo, + dwBytesTransferred, + pTarget->GetBlockSizeInBytes()); + } + + // check if we should print a progress dot + if (p->pProfile->GetProgress() != 0) + { + DWORD dwIOCnt = ++p->dwIOCnt; + if (dwIOCnt % p->pProfile->GetProgress() == 0) + { + printf("."); + } + } +} + +void completeIO(ThreadParameters *p, IORequest *pIORequest, DWORD dwBytesTransferred) +{ + if (p->pTimeSpan->GetMeasureLatency() || p->pTimeSpan->GetCalculateIopsStdDev()) + { + completeIOat(p, pIORequest, dwBytesTransferred, PerfTimer::GetTime()); + } + else + { + completeIOat(p, pIORequest, dwBytesTransferred, 0); + } +} + +/*****************************************************************************/ +// function called from worker thread +// performs synch I/O +// +static bool doWorkUsingSynchronousIO(ThreadParameters *p) +{ + BOOL fOk = true; + BOOL rslt = FALSE; + DWORD dwBytesTransferred; + size_t cIORequests = p->vIORequest.size(); + + while(g_bRun && !g_bThreadError) + { + DWORD nIssued = 0; + DWORD dwMinSleepTime = INFINITE; + for (size_t i = 0; i < cIORequests; i++) + { + IORequest *pIORequest = &p->vIORequest[i]; + Target *pTarget = pIORequest->GetNextTarget(); + + if (p->vThroughputMeters.size() != 0) + { + size_t iTarget = pTarget - &p->vTargets[0]; + ThroughputMeter *pThroughputMeter = &p->vThroughputMeters[iTarget]; + + DWORD dwSleepTime = pThroughputMeter->GetSleepTime(); + dwMinSleepTime = min(dwMinSleepTime, dwSleepTime); + if (pThroughputMeter->IsRunning() && dwSleepTime > 0) + { + continue; + } + } + + nIssued += 1; + rslt = issueNextIO(p, pIORequest, &dwBytesTransferred, false); + + if (!rslt) + { + PrintError("t[%u] error during %s error code: %u)\n", (UINT32)i, (pIORequest->GetIoType() == IOOperation::ReadIO ? "read" : "write"), GetLastError()); + fOk = false; + goto cleanup; + } + + completeIO(p, pIORequest, dwBytesTransferred); + } + + // if no IOs were issued, wait for the next scheduling time + if (!nIssued && dwMinSleepTime != INFINITE && dwMinSleepTime != 0) + { + p->pResults->WaitStats.ThrottleSleep += 1; + Sleep(dwMinSleepTime); + } + + assert(!g_bError); // at this point we shouldn't be seeing initialization error + } + +cleanup: + return fOk; +} + +/*****************************************************************************/ +// function called from worker thread +// performs asynch I/O using IO Completion Ports +// +static bool doWorkUsingIOCompletionPorts(ThreadParameters *p, HANDLE hCompletionPort) +{ + assert(nullptr != p); + assert(nullptr != hCompletionPort); + + BOOL fOk = true; + BOOL rslt = FALSE; + DWORD dwBytesTransferred; + OverlappedQueue overlappedQueue; + size_t cIORequests = p->vIORequest.size(); + BOOL fLatencyStats = p->pTimeSpan->GetMeasureLatency() || p->pTimeSpan->GetCalculateIopsStdDev(); + + for (size_t i = 0; i < cIORequests; i++) + { + overlappedQueue.Add(p->vIORequest[i].GetOverlapped()); + } + + // + // perform work + // + DWORD dwMinSleepTime = INFINITE; + DWORD dwWaitTime; + + OVERLAPPED_ENTRY ovlEntry[16]; + const ULONG cOvlEntryMax = _countof(ovlEntry) < (ULONG)cIORequests ? _countof(ovlEntry) : (ULONG)cIORequests; + ULONG cCompleted; + size_t cUntilThrottle = cIORequests; + + while(g_bRun && !g_bThreadError) + { + OVERLAPPED *pReadyOverlapped = overlappedQueue.Remove(); + IORequest *pIORequest = IORequest::OverlappedToIORequest(pReadyOverlapped); + (void) pIORequest->GetNextTarget(); + + // check throttles + if (p->vThroughputMeters.size() != 0) + { + ThroughputMeter *pThroughputMeter = &p->vThroughputMeters[pIORequest->GetCurrentTargetIndex()]; + + cUntilThrottle -= 1; + + DWORD dwSleepTime = pThroughputMeter->GetSleepTime(); + if (pThroughputMeter->IsRunning() && dwSleepTime > 0) + { + dwMinSleepTime = min(dwMinSleepTime, dwSleepTime); + overlappedQueue.Add(pReadyOverlapped); + + // continue if throttle not hit + if (cUntilThrottle) + { + continue; + } + + // at throttle, no IO to dispatch + pIORequest = NULL; + } + } + + // dispatch IO - skipped iff at throttle + if (pIORequest) + { + rslt = issueNextIO(p, pIORequest, &dwBytesTransferred, false); + + if (!rslt && GetLastError() != ERROR_IO_PENDING) + { + UINT32 iIORequest = (UINT32)(pIORequest - &p->vIORequest[0]); + PrintError("t[%u] error during %s error code: %u)\n", iIORequest, (pIORequest->GetIoType()== IOOperation::ReadIO ? "read" : "write"), GetLastError()); + fOk = false; + goto cleanup; + } + + if (rslt && pIORequest->GetCurrentTarget()->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) + { + completeIO(p, pIORequest, dwBytesTransferred); + overlappedQueue.Add(pReadyOverlapped); + + // a completed memory mapped IO resets the throttle so that we traverse + // back to it in fair-order before considering throttle again. + // note this will drop through to lookside for completions, not wait + dwMinSleepTime = INFINITE; + cUntilThrottle = overlappedQueue.GetCount(); + } + } + + // look for IO completion + // queue is fully dispatched: set wait, reset throttle wait + if (!overlappedQueue.GetCount()) + { + assert(!cUntilThrottle); + dwWaitTime = dwMinSleepTime = INFINITE; + p->pResults->WaitStats.Wait += 1; + } + + // queue is not fully dispatched ... + // if at the throttle, wait throttle time and reset + else if (!cUntilThrottle) + { + dwWaitTime = dwMinSleepTime; + dwMinSleepTime = INFINITE; + cUntilThrottle = overlappedQueue.GetCount(); + + if (cIORequests == cUntilThrottle) + { + // all throttled, none dispatched - just sleep + p->pResults->WaitStats.ThrottleSleep += 1; + Sleep(dwWaitTime); + continue; + } + else + { + // throttled, but some dispatched - wait for completions + p->pResults->WaitStats.ThrottleWait += 1; + } + } + + // queue is not fully dispatched ... + // if this run is not for latency stats, optimize for dispatch and + // skip completion lookasides + else if (!fLatencyStats) + { + continue; + } + + // else lookaside + else + { + dwWaitTime = 0; + p->pResults->WaitStats.Lookaside += 1; + } + + if (GetQueuedCompletionStatusEx(hCompletionPort, ovlEntry, cOvlEntryMax, &cCompleted, dwWaitTime, FALSE) != 0) + { + UINT64 ullCompletionTime = 0; + + if (fLatencyStats) + { + // single completion time estimate for all completions + ullCompletionTime = PerfTimer::GetTime(); + } + + for (ULONG i = 0; i < cCompleted; i++) + { + completeIOat(p, IORequest::OverlappedToIORequest(ovlEntry[i].lpOverlapped), ovlEntry[i].dwNumberOfBytesTransferred, ullCompletionTime); + overlappedQueue.Add(ovlEntry[i].lpOverlapped); + } + + // must reevaluate queue in fair order before next throttle + cUntilThrottle = overlappedQueue.GetCount(); + } + else + { + DWORD err = GetLastError(); + if (err != WAIT_TIMEOUT) + { + PrintError("error during overlapped IO operation (error code: %u)\n", err); + fOk = false; + goto cleanup; + } + } + + // stats for lookaside waits + if (dwWaitTime == 0) + { + p->pResults->WaitStats.LookasideCompletion[cCompleted < _countof(p->pResults->WaitStats.LookasideCompletion) ? cCompleted : _countof(p->pResults->WaitStats.LookasideCompletion) - 1] += 1; + } + } // end work loop + +cleanup: + return fOk; +} + +/*****************************************************************************/ +// I/O completion routine. used by ReadFileEx and WriteFileEx +// + +VOID CALLBACK fileIOCompletionRoutine(DWORD dwErrorCode, DWORD dwBytesTransferred, LPOVERLAPPED pOverlapped) +{ + assert(NULL != pOverlapped); + + BOOL rslt = FALSE; + ThreadParameters *p = (ThreadParameters *)pOverlapped->hEvent; + + assert(NULL != p); + + //check error code + if (0 != dwErrorCode) + { + PrintError("Thread %u failed executing an I/O operation (error code: %u)\n", p->ulThreadNo, dwErrorCode); + goto cleanup; + } + + IORequest *pIORequest = IORequest::OverlappedToIORequest(pOverlapped); + + completeIO(p, pIORequest, dwBytesTransferred); + + // start a new IO operation + if (g_bRun && !g_bThreadError) + { + (void) pIORequest->GetNextTarget(); + rslt = issueNextIO(p, pIORequest, NULL, true); + + if (!rslt) + { + PrintError("t[%u:%u] error during %s error code: %u)\n", p->ulThreadNo, pIORequest->GetCurrentTargetIndex(), (pIORequest->GetIoType() == IOOperation::ReadIO ? "read" : "write"), GetLastError()); + goto cleanup; + } + } + +cleanup: + return; +} + +/*****************************************************************************/ +// function called from worker thread +// performs asynch I/O using IO Completion Routines (ReadFileEx, WriteFileEx) +// +static bool doWorkUsingCompletionRoutines(ThreadParameters *p) +{ + assert(NULL != p); + bool fOk = true; + BOOL rslt = FALSE; + + // start IO operations + // completion routines will reissue 1:1 + UINT32 cIORequests = (UINT32)p->vIORequest.size(); + + for (size_t i = 0; i < cIORequests; i++) + { + IORequest *pIORequest = &p->vIORequest[i]; + + rslt = issueNextIO(p, pIORequest, NULL, true); + + if (!rslt) + { + PrintError("t[%u:%u] error during %s error code: %u)\n", p->ulThreadNo, pIORequest->GetCurrentTargetIndex(), (pIORequest->GetIoType() == IOOperation::ReadIO ? "read" : "write"), GetLastError()); + fOk = false; + goto cleanup; + } + } + + DWORD dwWaitResult = 0; + while( g_bRun && !g_bThreadError ) + { + dwWaitResult = WaitForSingleObjectEx(p->hEndEvent, INFINITE, TRUE); + + assert(WAIT_IO_COMPLETION == dwWaitResult || (WAIT_OBJECT_0 == dwWaitResult && (!g_bRun || g_bThreadError))); + + //check WaitForSingleObjectEx status + if( WAIT_IO_COMPLETION != dwWaitResult && WAIT_OBJECT_0 != dwWaitResult ) + { + PrintError("Error in thread %u during WaitForSingleObjectEx (in completion routines)\n", p->ulThreadNo); + fOk = false; + goto cleanup; + } + } +cleanup: + return fOk; +} + +struct UniqueTarget { + string path; + TargetCacheMode caching; + PRIORITY_HINT priority; + DWORD dwDesiredAccess; + DWORD dwFlags; + + bool operator < (const struct UniqueTarget &ut) const { + if (path < ut.path) { + return true; + } + else if (ut.path < path) { + return false; + } + + if (caching < ut.caching) { + return true; + } + else if (ut.caching < caching) { + return false; + } + + if (priority < ut.priority) { + return true; + } + else if (ut.priority < priority) { + return false; + } + + if (dwDesiredAccess < ut.dwDesiredAccess) { + return true; + } + else if (ut.dwDesiredAccess < dwDesiredAccess) { + return false; + } + + if (dwFlags < ut.dwFlags) { + return true; + } + + return false; + } +}; + +/*****************************************************************************/ +// worker thread function +// +DWORD WINAPI threadFunc(LPVOID cookie) +{ + bool fOk = true; + bool fAnyMappedIo = false; + bool fAllMappedIo = true; + ThreadParameters *p = reinterpret_cast(cookie); + HANDLE hCompletionPort = nullptr; + + // + // A single file can be specified in multiple targets, so only open one + // handle for each unique file. + // + + vector vhUniqueHandles; + map mHandleMap; + + bool fCalculateIopsStdDev = p->pTimeSpan->GetCalculateIopsStdDev(); + UINT64 ioBucketDuration = 0; + UINT32 expectedNumberOfBuckets = 0; + if(fCalculateIopsStdDev) + { + UINT32 ioBucketDurationInMilliseconds = p->pTimeSpan->GetIoBucketDurationInMilliseconds(); + ioBucketDuration = PerfTimer::MillisecondsToPerfTime(ioBucketDurationInMilliseconds); + expectedNumberOfBuckets = Util::QuotientCeiling(p->pTimeSpan->GetDuration() * 1000, ioBucketDurationInMilliseconds); + } + + // apply affinity. The specific assignment is provided in the thread profile up front. + if (!p->pTimeSpan->GetDisableAffinity()) + { + GROUP_AFFINITY GroupAffinity; + + PrintVerbose(p->pProfile->GetVerbose(), "thread %u: affinitizing to Group %u / CPU %u\n", p->ulThreadNo, p->wGroupNum, p->bProcNum); + SetProcGroupMask(p->wGroupNum, p->bProcNum, &GroupAffinity); + + HANDLE hThread = GetCurrentThread(); + if (SetThreadGroupAffinity(hThread, &GroupAffinity, nullptr) == FALSE) + { + PrintError("Error setting affinity mask in thread %u\n", p->ulThreadNo); + fOk = false; + goto cleanup; + } + } + + // adjust thread token if large pages are needed + for (auto pTarget = p->vTargets.begin(); pTarget != p->vTargets.end(); pTarget++) + { + if (pTarget->GetUseLargePages()) + { + if (!SetPrivilege(SE_LOCK_MEMORY_NAME)) + { + fOk = false; + goto cleanup; + } + break; + } + } + + UINT32 cIORequests = p->GetTotalRequestCount(); + + size_t iTarget = 0; + for (auto pTarget = p->vTargets.begin(); pTarget != p->vTargets.end(); pTarget++) + { + bool fPhysical = false; + bool fPartition = false; + + string sPath(pTarget->GetPath()); + const char *filename = sPath.c_str(); + + const char *fname = nullptr; //filename (can point to physFN) + char physFN[32]; //disk/partition name + + if (NULL == filename || NULL == *(filename)) + { + PrintError("FATAL ERROR: invalid filename\n"); + fOk = false; + goto cleanup; + } + + //check if it is a physical drive + if ('#' == *filename && NULL != *(filename + 1)) + { + if (pTarget->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) + { + PrintError("Memory mapped I/O is not supported on physical drives\n"); + fOk = false; + goto cleanup; + } + UINT32 nDriveNo = (UINT32)atoi(filename + 1); + fPhysical = true; + sprintf_s(physFN, 32, "\\\\.\\PhysicalDrive%u", nDriveNo); + fname = physFN; + } + + //check if it is a partition + if (!fPhysical && NULL != *(filename + 1) && NULL == *(filename + 2) && isalpha((unsigned char)filename[0]) && ':' == filename[1]) + { + if (pTarget->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) + { + PrintError("Memory mapped I/O is not supported on partitions\n"); + fOk = false; + goto cleanup; + } + fPartition = true; + + sprintf_s(physFN, 32, "\\\\.\\%c:", filename[0]); + fname = physFN; + } + + //check if it is a regular file + if (!fPhysical && !fPartition) + { + fname = sPath.c_str(); + } + + // get/set file flags + DWORD dwFlags = pTarget->GetCreateFlags(cIORequests > 1); + DWORD dwDesiredAccess = 0; + if (pTarget->GetWriteRatio() == 0) + { + dwDesiredAccess = GENERIC_READ; + } + else if (pTarget->GetWriteRatio() == 100) + { + dwDesiredAccess = GENERIC_WRITE; + } + else + { + dwDesiredAccess = GENERIC_READ | GENERIC_WRITE; + } + + if (pTarget->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) + { + dwDesiredAccess = GENERIC_READ | GENERIC_WRITE; + fAnyMappedIo = true; + } + else + { + fAllMappedIo = false; + } + + HANDLE hFile; + UniqueTarget ut; + ut.path = sPath; + ut.priority = pTarget->GetIOPriorityHint(); + ut.caching = pTarget->GetCacheMode(); + ut.dwDesiredAccess = dwDesiredAccess; + ut.dwFlags = dwFlags; + + if (mHandleMap.find(ut) == mHandleMap.end()) { + hFile = CreateFile(fname, + dwDesiredAccess, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, //security + OPEN_EXISTING, + dwFlags, //flags + nullptr); //template file + if (INVALID_HANDLE_VALUE == hFile) + { + // TODO: error out + PrintError("Error opening file: %s [%u]\n", sPath.c_str(), GetLastError()); + fOk = false; + goto cleanup; + } + + if (pTarget->GetCacheMode() == TargetCacheMode::DisableLocalCache) + { + DWORD Status = DisableLocalCache(hFile); + if (Status != ERROR_SUCCESS) + { + PrintError("Failed to disable local caching (error %u). NOTE: only supported on remote filesystems with Windows 8 or newer.\n", Status); + fOk = false; + goto cleanup; + } + } + + //set IO priority + if (pTarget->GetIOPriorityHint() != IoPriorityHintNormal) + { + _declspec(align(8)) FILE_IO_PRIORITY_HINT_INFO hintInfo; + hintInfo.PriorityHint = pTarget->GetIOPriorityHint(); + if (!SetFileInformationByHandle(hFile, FileIoPriorityHintInfo, &hintInfo, sizeof(hintInfo))) + { + PrintError("Error setting IO priority for file: %s [%u]\n", sPath.c_str(), GetLastError()); + fOk = false; + goto cleanup; + } + } + + mHandleMap[ut] = (UINT32)vhUniqueHandles.size(); + vhUniqueHandles.push_back(hFile); + } + else { + hFile = vhUniqueHandles[mHandleMap[ut]]; + } + + p->vhTargets.push_back(hFile); + + // obtain file/disk/partition size + { + UINT64 fsize = 0; //file size + + //check if it is a disk + if (fPhysical) + { + fsize = GetPhysicalDriveSize(hFile); + } + // check if it is a partition + else if (fPartition) + { + fsize = GetPartitionSize(hFile); + } + // it has to be a regular file + else + { + ULARGE_INTEGER ulsize; + + ulsize.LowPart = GetFileSize(hFile, &ulsize.HighPart); + if (INVALID_FILE_SIZE == ulsize.LowPart && GetLastError() != NO_ERROR) + { + PrintError("Error getting file size\n"); + fOk = false; + goto cleanup; + } + else + { + fsize = ulsize.QuadPart; + } + } + + // check if file size is valid (if it's == 0, it won't be useful) + if (0 == fsize) + { + // TODO: error out + PrintError("ERROR: target size could not be determined\n"); + fOk = false; + goto cleanup; + } + + if (fsize < pTarget->GetMaxFileSize()) + { + PrintError("WARNING: file size %I64u is less than MaxFileSize %I64u\n", fsize, pTarget->GetMaxFileSize()); + } + + // + // Build target state. + // + + p->vTargetStates.emplace_back( + p, + iTarget, + fsize); + + // + // Ensure this thread can start given stride/size of target. + // + + if (!p->vTargetStates[iTarget].CanStart()) + { + PrintError("The file is too small. File: '%s' relative thread %u: file size: %I64u, base offset: %I64u, thread stride: %I64u, block size: %u\n", + pTarget->GetPath().c_str(), + p->ulRelativeThreadNo, + fsize, + pTarget->GetBaseFileOffsetInBytes(), + pTarget->GetThreadStrideInBytes(), + pTarget->GetBlockSizeInBytes()); + fOk = false; + goto cleanup; + } + } + + PrintVerbose(p->pProfile->GetVerbose(), "thread %u: file '%s' relative thread %u (random seed: %u)\n", + p->ulThreadNo, + pTarget->GetPath().c_str(), + p->ulRelativeThreadNo, + p->ulRandSeed); + + if (pTarget->GetRandomRatio() > 0) + { + PrintVerbose(p->pProfile->GetVerbose(), "thread %u: %u%% random IO\n", + p->ulThreadNo, + pTarget->GetRandomRatio()); + } + else + { + PrintVerbose(p->pProfile->GetVerbose(), "thread %u: %ssequential IO\n", + p->ulThreadNo, + pTarget->GetUseInterlockedSequential() ? "interlocked ":""); + } + + // allocate memory for a data buffer + if (!p->AllocateAndFillBufferForTarget(*pTarget)) + { + PrintError("ERROR: Could not allocate a buffer for target '%s'. Error code: 0x%x\n", pTarget->GetPath().c_str(), GetLastError()); + fOk = false; + goto cleanup; + } + + // initialize memory mapped views of files + if (pTarget->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) + { + NTSTATUS status; + PVOID nvToken; + + pTarget->SetMappedViewFileHandle(hFile); + if (!p->InitializeMappedViewForTarget(*pTarget, dwDesiredAccess)) + { + PrintError("ERROR: Could not map view for target '%s'. Error code: 0x%x\n", pTarget->GetPath().c_str(), GetLastError()); + fOk = false; + goto cleanup; + } + + if (pTarget->GetWriteThroughMode() == WriteThroughMode::On && nullptr == g_pfnRtlCopyMemoryNonTemporal) + { + PrintError("ERROR: Windows runtime environment does not support the non-temporal memory copy API for target '%s'.\n", pTarget->GetPath().c_str()); + fOk = false; + goto cleanup; + } + + if ((pTarget->GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::NonVolatileMemory) || (pTarget->GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain)) + { + // RtlGetNonVolatileToken() works only on DAX enabled PMEM devices. + if (g_pfnRtlGetNonVolatileToken != nullptr && g_pfnRtlFreeNonVolatileToken != nullptr) + { + status = g_pfnRtlGetNonVolatileToken(pTarget->GetMappedView(), (SIZE_T) pTarget->GetFileSize(), &nvToken); + if (!NT_SUCCESS(status)) + { + PrintError("ERROR: Could not get non-volatile token for target '%s'. Error code: 0x%x\n", pTarget->GetPath().c_str(), GetLastError()); + fOk = false; + goto cleanup; + } + pTarget->SetMemoryMappedIoNvToken(nvToken); + } + else + { + PrintError("ERROR: Windows runtime environment does not support the non-volatile memory flushing APIs for target '%s'.\n", pTarget->GetPath().c_str()); + fOk = false; + goto cleanup; + } + } + } + + iTarget++; + } + + // TODO: copy parameters for better memory locality? + // TODO: tell the main thread we're ready + + p->pResults->vTargetResults.clear(); + p->pResults->vTargetResults.resize(p->vTargets.size()); + + for (size_t i = 0; i < p->vTargets.size(); i++) + { + p->pResults->vTargetResults[i].sPath = p->vTargets[i].GetPath(); + p->pResults->vTargetResults[i].ullFileSize = p->vTargetStates[i].TargetSize(); + + if(fCalculateIopsStdDev) + { + p->pResults->vTargetResults[i].readBucketizer.Initialize(ioBucketDuration, expectedNumberOfBuckets); + p->pResults->vTargetResults[i].writeBucketizer.Initialize(ioBucketDuration, expectedNumberOfBuckets); + } + + // + // Copy effective distribution range to results for reporting (may be empty) + // + + p->pResults->vTargetResults[i].vDistributionRange = p->vTargetStates[i]._vDistributionRange; + } + + // + // fill the IORequest structures + // + + p->vIORequest.clear(); + + if (p->pTimeSpan->GetThreadCount() != 0 && + p->pTimeSpan->GetRequestCount() != 0) + { + p->vIORequest.resize(cIORequests, IORequest(p->pRand)); + + for (UINT32 iIORequest = 0; iIORequest < cIORequests; iIORequest++) + { + p->vIORequest[iIORequest].SetRequestIndex(iIORequest); + + for (unsigned int iFile = 0; iFile < p->vTargets.size(); iFile++) + { + Target *pTarget = &p->vTargets[iFile]; + const vector vThreadTargets = pTarget->GetThreadTargets(); + UINT32 ulWeight = pTarget->GetWeight(); + + for (UINT32 iThreadTarget = 0; iThreadTarget < vThreadTargets.size(); iThreadTarget++) + { + if (vThreadTargets[iThreadTarget].GetThread() == p->ulRelativeThreadNo) + { + if (vThreadTargets[iThreadTarget].GetWeight() != 0) + { + ulWeight = vThreadTargets[iThreadTarget].GetWeight(); + } + break; + } + } + + // + // Parallel async is not supported with -O for exactly this reason, + // and is validated in the profile before reaching here. Document this + // with the assert in comparison to the code in the non-O case below. + // Parallel depends on the IORequest being for a single file only (the + // seq offset is in the IORequest itself). + // + + assert(pTarget->GetUseParallelAsyncIO() == false); + + p->vIORequest[iIORequest].AddTarget(pTarget, ulWeight); + } + } + } + else + { + for (unsigned int iFile = 0; iFile < p->vTargets.size(); iFile++) + { + Target *pTarget = &p->vTargets[iFile]; + + for (DWORD iRequest = 0; iRequest < pTarget->GetRequestCount(); ++iRequest) + { + IORequest ioRequest(p->pRand); + ioRequest.AddTarget(pTarget, 1); + ioRequest.SetRequestIndex(iRequest); + if (pTarget->GetUseParallelAsyncIO()) + { + p->vTargetStates[iFile].InitializeParallelAsyncIORequest(ioRequest); + } + + p->vIORequest.push_back(ioRequest); + } + } + } + + // + // fill the throughput meter structures + // + size_t cTargets = p->vTargets.size(); + bool fUseThrougputMeter = false; + for (size_t i = 0; i < cTargets; i++) + { + ThroughputMeter throughputMeter; + Target *pTarget = &p->vTargets[i]; + DWORD dwBurstSize = pTarget->GetBurstSize(); + if (p->pTimeSpan->GetThreadCount() > 0) + { + if (pTarget->GetThreadTargets().size() == 0) + { + dwBurstSize /= p->pTimeSpan->GetThreadCount(); + } + else + { + dwBurstSize /= (DWORD)pTarget->GetThreadTargets().size(); + } + } + else + { + dwBurstSize /= pTarget->GetThreadsPerFile(); + } + + if (pTarget->GetThroughputInBytesPerMillisecond() > 0 || pTarget->GetThinkTime() > 0) + { + fUseThrougputMeter = true; + throughputMeter.Start(pTarget->GetThroughputInBytesPerMillisecond(), pTarget->GetBlockSizeInBytes(), pTarget->GetThinkTime(), dwBurstSize); + } + + p->vThroughputMeters.push_back(throughputMeter); + } + + if (!fUseThrougputMeter) + { + p->vThroughputMeters.clear(); + } + + //FUTURE EXTENSION: enable asynchronous I/O even if only 1 outstanding I/O per file (requires another parameter) + if (cIORequests == 1 || fAllMappedIo) + { + //synchronous IO - no setup needed + } + else if (p->pTimeSpan->GetCompletionRoutines() && !fAnyMappedIo) + { + //in case of completion routines hEvent field is not used, + //so we can use it to pass a pointer to the thread parameters + for (UINT32 iIORequest = 0; iIORequest < cIORequests; iIORequest++) { + OVERLAPPED *pOverlapped; + + pOverlapped = p->vIORequest[iIORequest].GetOverlapped(); + pOverlapped->hEvent = (HANDLE)p; + } + } + else + { + // + // create IO completion port if not doing completion routines or synchronous IO + // + for (unsigned int i = 0; i < vhUniqueHandles.size(); i++) + { + hCompletionPort = CreateIoCompletionPort(vhUniqueHandles[i], hCompletionPort, 0, 1); + if (nullptr == hCompletionPort) + { + PrintError("unable to create IO completion port (error code: %u)\n", GetLastError()); + fOk = false; + goto cleanup; + } + } + } + + // + // wait for a signal to start + // + PrintVerbose(p->pProfile->GetVerbose(), "thread %u: waiting for a signal to start\n", p->ulThreadNo); + if( WAIT_FAILED == WaitForSingleObject(p->hStartEvent, INFINITE) ) + { + PrintError("Waiting for a signal to start failed (error code: %u)\n", GetLastError()); + fOk = false; + goto cleanup; + } + PrintVerbose(p->pProfile->GetVerbose(), "thread %u: received signal to start\n", p->ulThreadNo); + + //check if everything is ok + if (g_bError) + { + fOk = false; + goto cleanup; + } + + //error handling and memory freeing is done in doWorkUsingIOCompletionPorts and doWorkUsingCompletionRoutines + if (cIORequests == 1 || fAllMappedIo) + { + // use synchronous IO (it will also clse the event) + if (!doWorkUsingSynchronousIO(p)) + { + fOk = false; + goto cleanup; + } + } + else if (!p->pTimeSpan->GetCompletionRoutines() || fAnyMappedIo) + { + // use IO Completion Ports (it will also close the I/O completion port) + if (!doWorkUsingIOCompletionPorts(p, hCompletionPort)) + { + fOk = false; + goto cleanup; + } + } + else + { + //use completion routines + if (!doWorkUsingCompletionRoutines(p)) + { + fOk = false; + goto cleanup; + } + } + + assert(!g_bError); // at this point we shouldn't be seeing initialization error + + // save results + +cleanup: + if (!fOk) + { + g_bThreadError = TRUE; + } + + // free memory allocated with VirtualAlloc + for (auto i = p->vpDataBuffers.begin(); i != p->vpDataBuffers.end(); i++) + { + if (nullptr != *i) + { +#pragma prefast(suppress:6001, "Prefast does not understand this vector will only contain validly allocated buffer pointers") + VirtualFree(*i, 0, MEM_RELEASE); + } + } + + // free NV tokens + for (auto i = p->vTargets.begin(); i != p->vTargets.end(); i++) + { + if (i->GetMemoryMappedIoNvToken() != nullptr && g_pfnRtlFreeNonVolatileToken != nullptr) + { + g_pfnRtlFreeNonVolatileToken(i->GetMemoryMappedIoNvToken()); + i->SetMemoryMappedIoNvToken(nullptr); + } + } + + // close files + for (auto i = vhUniqueHandles.begin(); i != vhUniqueHandles.end(); i++) + { + CloseHandle(*i); + } + + // close completion ports + if (hCompletionPort != nullptr) + { + CloseHandle(hCompletionPort); + } + + delete p->pRand; + delete p; + + // notify master thread that we've finished + InterlockedDecrement(&g_lRunningThreadsCount); + + return fOk ? 1 : 0; +} + +/*****************************************************************************/ +struct ETWSessionInfo IORequestGenerator::_GetResultETWSession(const EVENT_TRACE_PROPERTIES *pTraceProperties) const +{ + struct ETWSessionInfo session = {}; + if (nullptr != pTraceProperties) + { + session.lAgeLimit = pTraceProperties->AgeLimit; + session.ulBufferSize = pTraceProperties->BufferSize; + session.ulBuffersWritten = pTraceProperties->BuffersWritten; + session.ulEventsLost = pTraceProperties->EventsLost; + session.ulFlushTimer = pTraceProperties->FlushTimer; + session.ulFreeBuffers = pTraceProperties->FreeBuffers; + session.ulLogBuffersLost = pTraceProperties->LogBuffersLost; + session.ulMaximumBuffers = pTraceProperties->MaximumBuffers; + session.ulMinimumBuffers = pTraceProperties->MinimumBuffers; + session.ulNumberOfBuffers = pTraceProperties->NumberOfBuffers; + session.ulRealTimeBuffersLost = pTraceProperties->RealTimeBuffersLost; + } + return session; +} + +DWORD IORequestGenerator::_CreateDirectoryPath(const char *pszPath) const +{ + char *c = nullptr; //variable used to browse the path + char dirPath[MAX_PATH]; //copy of the path (it will be altered) + + //only support absolute paths that specify the drive letter + if (pszPath[0] == '\0' || pszPath[1] != ':') + { + return ERROR_NOT_SUPPORTED; + } + + if (strcpy_s(dirPath, _countof(dirPath), pszPath) != 0) + { + return ERROR_BUFFER_OVERFLOW; + } + + c = dirPath; + while('\0' != *c) + { + if ('\\' == *c) + { + //skip the first one as it will be the drive name + if (c-dirPath >= 3) + { + *c = '\0'; + //create directory if it doesn't exist + if (GetFileAttributes(dirPath) == INVALID_FILE_ATTRIBUTES) + { + if (CreateDirectory(dirPath, NULL) == FALSE) + { + return GetLastError(); + } + } + *c = L'\\'; + } + } + + c++; + } + + return ERROR_SUCCESS; +} + +/*****************************************************************************/ +// create a file of the given size +// +bool IORequestGenerator::_CreateFile(UINT64 ullFileSize, const char *pszFilename, bool fZeroBuffers, bool fVerbose) const +{ + bool fSlowWrites = false; + PrintVerbose(fVerbose, "Creating file '%s' of size %I64u.\n", pszFilename, ullFileSize); + + //enable SE_MANAGE_VOLUME_NAME privilege, required to set valid size of a file + if (!SetPrivilege(SE_MANAGE_VOLUME_NAME, "WARNING:")) + { + PrintError("WARNING: Could not set privileges for setting valid file size; will use a slower method of preparing the file\n", GetLastError()); + fSlowWrites = true; + } + + // there are various forms of paths we do not support creating subdir hierarchies + // for - relative and unc paths specifically. this is fine, and not neccesary to + // warn about. we can add support in the future. + DWORD dwError = _CreateDirectoryPath(pszFilename); + if (dwError != ERROR_SUCCESS && dwError != ERROR_NOT_SUPPORTED) + { + PrintError("WARNING: Could not create intermediate directory (error code: %u)\n", dwError); + } + + // create handle to the file + HANDLE hFile = CreateFile(pszFilename, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + nullptr); + if (INVALID_HANDLE_VALUE == hFile) + { + PrintError("Could not create the file (error code: %u)\n", GetLastError()); + return false; + } + + if (ullFileSize > 0) + { + LARGE_INTEGER li; + li.QuadPart = ullFileSize; + + LARGE_INTEGER liNewFilePointer; + + if (!SetFilePointerEx(hFile, li, &liNewFilePointer, FILE_BEGIN)) + { + PrintError("Could not set file pointer during file creation when extending file (error code: %u)\n", GetLastError()); + CloseHandle(hFile); + return false; + } + if (liNewFilePointer.QuadPart != li.QuadPart) + { + PrintError("File pointer improperly moved during file creation when extending file\n"); + CloseHandle(hFile); + return false; + } + + //extends file (warning! this is a kind of "reservation" of space; valid size of the file is still 0!) + if (!SetEndOfFile(hFile)) + { + PrintError("Error setting end of file (error code: %u)\n", GetLastError()); + CloseHandle(hFile); + return false; + } + //try setting valid size of the file (privileges for that are enabled before CreateFile) + if (!fSlowWrites && !SetFileValidData(hFile, ullFileSize)) + { + PrintError("WARNING: Could not set valid file size (error code: %u); trying a slower method of filling the file" + " (this does not affect performance, just makes the test preparation longer)\n", + GetLastError()); + fSlowWrites = true; + } + + //if setting valid size couldn't be performed, fill in the file by simply writing to it (slower) + if (fSlowWrites) + { + li.QuadPart = 0; + if (!SetFilePointerEx(hFile, li, &liNewFilePointer, FILE_BEGIN)) + { + PrintError("Could not set file pointer during file creation (error code: %u)\n", GetLastError()); + CloseHandle(hFile); + return false; + } + if (liNewFilePointer.QuadPart != li.QuadPart) + { + PrintError("File pointer improperly moved during file creation\n"); + CloseHandle(hFile); + return false; + } + + UINT32 ulBufSize; + UINT64 ullRemainSize; + + ulBufSize = 1024*1024; + if (ullFileSize < (UINT64)ulBufSize) + { + ulBufSize = (UINT32)ullFileSize; + } + + vector vBuf(ulBufSize); + for (UINT32 i=0; i 0) + { + DWORD dwBytesWritten; + if ((UINT64)ulBufSize > ullRemainSize) + { + ulBufSize = (UINT32)ullRemainSize; + } + + if (!WriteFile(hFile, &vBuf[0], ulBufSize, &dwBytesWritten, NULL)) + { + PrintError("Error while writng during file creation (error code: %u)\n", GetLastError()); + CloseHandle(hFile); + return false; + } + + if (dwBytesWritten != ulBufSize) + { + PrintError("Improperly written data during file creation\n"); + CloseHandle(hFile); + return false; + } + + ullRemainSize -= ulBufSize; + } + } + } + + //if compiled with debug support, check file size +#ifndef NDEBUG + LARGE_INTEGER li; + if( GetFileSizeEx(hFile, &li) ) + { + assert(li.QuadPart == (LONGLONG)ullFileSize); + } +#endif + + CloseHandle(hFile); + + return true; +} + +/*****************************************************************************/ +void IORequestGenerator::_TerminateWorkerThreads(vector& vhThreads) const +{ + for (UINT32 x = 0; x < vhThreads.size(); ++x) + { + assert(NULL != vhThreads[x]); +#pragma warning( push ) +#pragma warning( disable : 6258 ) + if (!TerminateThread(vhThreads[x], 0)) + { + PrintError("Warning: unable to terminate worker thread %u\n", x); + } +#pragma warning( pop ) + } +} +/*****************************************************************************/ +void IORequestGenerator::_AbortWorkerThreads(HANDLE hStartEvent, vector& vhThreads) const +{ + assert(NULL != hStartEvent); + + if (NULL == hStartEvent) + { + return; + } + + g_bError = TRUE; + if (!SetEvent(hStartEvent)) + { + PrintError("Error signaling start event\n"); + _TerminateWorkerThreads(vhThreads); + } + else + { + //FUTURE EXTENSION: maximal timeout may be added here (and below) + while (g_lRunningThreadsCount > 0) + { + Sleep(100); + } + } +} + +/*****************************************************************************/ +bool IORequestGenerator::_StopETW(bool fUseETW, TRACEHANDLE hTraceSession) const +{ + bool fOk = true; + if (fUseETW) + { + PEVENT_TRACE_PROPERTIES pETWSession = StopETWSession(hTraceSession); + if (nullptr == pETWSession) + { + PrintError("Error stopping ETW session\n"); + fOk = false; + } + else + { + free(pETWSession); + } + } + return fOk; +} + +/*****************************************************************************/ +// initializes all global parameters +// +void IORequestGenerator::_InitializeGlobalParameters() +{ + g_lRunningThreadsCount = 0; //number of currently running worker threads + g_bRun = TRUE; //used for letting threads know that they should stop working + + g_bThreadError = FALSE; //true means that an error has occured in one of the threads + g_bTracing = FALSE; //true means that ETW is turned on + + _hNTDLL = nullptr; //handle to ntdll.dll + g_bError = FALSE; //true means there was fatal error during intialization and threads shouldn't perform their work +} + +bool IORequestGenerator::_PrecreateFiles(Profile& profile) const +{ + bool fOk = true; + + if (profile.GetPrecreateFiles() != PrecreateFiles::None) + { + vector vFilesToCreate = _GetFilesToPrecreate(profile); + vector vCreatedFiles; + for (auto file : vFilesToCreate) + { + fOk = _CreateFile(file.ullFileSize, file.sPath.c_str(), file.fZeroWriteBuffers, profile.GetVerbose()); + if (!fOk) + { + break; + } + vCreatedFiles.push_back(file.sPath); + } + + if (fOk) + { + profile.MarkFilesAsPrecreated(vCreatedFiles); + } + } + + return fOk; +} + +// bool IORequestGenerator::GenerateRequests(Profile& profile, IResultParser& resultParser, struct Synchronization *pSynch) +/// for CrystalDiskMark +bool IORequestGenerator::GenerateRequests(Profile& profile, IResultParser& resultParser, struct Synchronization *pSynch, int* totalScore, double* averageLatency) +{ + bool fOk = _PrecreateFiles(profile); + if (fOk) + { + const vector& vTimeSpans = profile.GetTimeSpans(); + vector vResults(vTimeSpans.size()); + for (size_t i = 0; fOk && (i < vTimeSpans.size()); i++) + { + PrintVerbose(profile.GetVerbose(), "Generating requests for timespan %u.\n", i + 1); + fOk = _GenerateRequestsForTimeSpan(profile, vTimeSpans[i], vResults[i], pSynch); + } + + // TODO: show results only for timespans that succeeded + SystemInformation system; + EtwResultParser::ParseResults(vResults); + string sResults = resultParser.ParseResults(profile, system, vResults); + printf("%s", sResults.c_str()); + fflush(stdout); + + /// for CrystalDiskMark + *totalScore = resultParser.GetTotalScore() * 10; + *averageLatency = resultParser.GetAverageLatency(); + } + + return fOk; +} + +bool IORequestGenerator::_GenerateRequestsForTimeSpan(const Profile& profile, const TimeSpan& timeSpan, Results& results, struct Synchronization *pSynch) +{ + //FUTURE EXTENSION: add new I/O capabilities presented in Longhorn + //FUTURE EXTENSION: add a check if the folder is compressed (cache is always enabled in case of compressed folders) + + //check if I/O request generator is already running + LONG lGenState = InterlockedExchange(&g_lGeneratorRunning, 1); + if (1 == lGenState) + { + PrintError("FATAL ERROR: I/O Request Generator already running\n"); + return false; + } + + //initialize all global parameters (in case of second run, after the first one is finished) + _InitializeGlobalParameters(); + + HANDLE hStartEvent = nullptr; // start event (used to inform the worker threads that they should start the work) + HANDLE hEndEvent = nullptr; // end event (used only in case of completin routines (not for IO Completion Ports)) + + memset(&g_EtwEventCounters, 0, sizeof(struct ETWEventCounters)); // reset all etw event counters + + bool fUseETW = profile.GetEtwEnabled(); //true if user wants ETW + + // + // load dlls + // + assert(nullptr == _hNTDLL); + if (!_LoadDLLs()) + { + PrintError("Error loading NtQuerySystemInformation\n"); + return false; + } + + //FUTURE EXTENSION: check for conflicts in alignment (when cache is turned off only sector aligned I/O are permitted) + //FUTURE EXTENSION: check if file sizes are enough to have at least first requests not wrapping around + + Random r; + vector vTargets = timeSpan.GetTargets(); + // allocate memory for random data write buffers + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + if ((i->GetRandomDataWriteBufferSize() > 0) && !i->AllocateAndFillRandomDataWriteBuffer(&r)) + { + return false; + } + } + + // check if user wanted to create a file + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + if ((i->GetFileSize() > 0) && (i->GetPrecreated() == false)) + { + string str = i->GetPath(); + if (str.empty()) + { + PrintError("You have to provide a filename\n"); + return false; + } + + //skip physical drives and partitions + if ('#' == str[0] || (':' == str[1] && '\0' == str[2])) + { + continue; + } + + //create only regular files + if (!_CreateFile(i->GetFileSize(), str.c_str(), i->GetZeroWriteBuffers(), profile.GetVerbose())) + { + return false; + } + } + } + + // get thread count + UINT32 cThreads = timeSpan.GetThreadCount(); + if (cThreads < 1) + { + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + cThreads += i->GetThreadsPerFile(); + } + } + + // allocate memory for thread handles + vector vhThreads(cThreads); + + // + // allocate memory for performance counters + // + vector vPerfInit(g_SystemInformation.processorTopology._ulProcessorCount); + vector vPerfDone(g_SystemInformation.processorTopology._ulProcessorCount); + vector vPerfDiff(g_SystemInformation.processorTopology._ulProcessorCount); + + // + // create start event + // + hStartEvent = CreateEvent(NULL, TRUE, FALSE, ""); + if (NULL == hStartEvent) + { + PrintError("Error creating the start event\n"); + return false; + } + + // + // create end event + // + if (timeSpan.GetCompletionRoutines()) + { + hEndEvent = CreateEvent(NULL, TRUE, FALSE, ""); + if (NULL == hEndEvent) + { + PrintError("Error creating the end event\n"); + return false; + } + } + + // + // set to high priority to ensure the controller thread gets to run immediately + // when signalled. + // + + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); + + // + // create the threads + // + + g_bRun = TRUE; + + // gather affinity information, and move to the first active processor + const auto& vAffinity = timeSpan.GetAffinityAssignments(); + WORD wGroupCtr = 0; + BYTE bProcCtr = 0; + g_SystemInformation.processorTopology.GetActiveGroupProcessor(wGroupCtr, bProcCtr, false); + + volatile bool fAccountingOn = false; + UINT64 ullStartTime; //start time + UINT64 ullTimeDiff; //elapsed test time (in units returned by QueryPerformanceCounter) + vector vullSharedSequentialOffsets(vTargets.size(), 0); + + results.vThreadResults.clear(); + results.vThreadResults.resize(cThreads); + for (UINT32 iThread = 0; iThread < cThreads; ++iThread) + { + PrintVerbose(profile.GetVerbose(), "creating thread %u\n", iThread); + ThreadParameters *cookie = new ThreadParameters(); // threadFunc is going to free the memory + if (nullptr == cookie) + { + PrintError("FATAL ERROR: could not allocate memory\n"); + _AbortWorkerThreads(hStartEvent, vhThreads); + return false; + } + + // each thread has a different random seed + Random *pRand = new Random(timeSpan.GetRandSeed() + iThread); + if (nullptr == pRand) + { + PrintError("FATAL ERROR: could not allocate memory\n"); + _AbortWorkerThreads(hStartEvent, vhThreads); + delete cookie; + return false; + } + + UINT32 ulRelativeThreadNo = 0; + + if (timeSpan.GetThreadCount() > 0) + { + // fixed thread mode: threads operate on specified files + // and receive the entire seq index array. + // relative thread number is the same as thread number. + cookie->pullSharedSequentialOffsets = &vullSharedSequentialOffsets[0]; + ulRelativeThreadNo = iThread; + for (auto i = vTargets.begin(); + i != vTargets.end(); + i++) + { + const vector vThreadTargets = i->GetThreadTargets(); + + // no thread targets specified - add to all threads + if (vThreadTargets.size() == 0) + { + cookie->vTargets.push_back(*i); + } + else + { + // check if the target should be added to the current thread + for (UINT32 iThreadTarget = 0; iThreadTarget < vThreadTargets.size(); iThreadTarget++) + { + if (vThreadTargets[iThreadTarget].GetThread() == iThread) + { + // confirm copy constructor? + cookie->vTargets.push_back(*i); + break; + } + } + } + } + } + else + { + size_t cAssignedThreads = 0; + size_t cBaseThread = 0; + auto psi = vullSharedSequentialOffsets.begin(); + for (auto i = vTargets.begin(); + i != vTargets.end(); + i++, psi++) + { + // per-file thread mode: groups of threads operate on individual files + // and receive the specific seq index for their file (note: singular). + // loop up through the targets to assign thread n to the appropriate file. + // relative thread number is file-relative, so keep track of the base + // thread number for the file and calculate relative to that. + // + // ex: two files, two threads per file + // t0: rt0 for f0 (cAssigned = 2, cBase = 0) + // t1: rt1 for f0 (cAssigned = 2, cBase = 0) + // t2: rt0 for f1 (cAssigned = 4, cBase = 2) + // t3: rt1 for f1 (cAssigned = 4, cBase = 2) + + cAssignedThreads += i->GetThreadsPerFile(); + if (iThread < cAssignedThreads) + { + // confirm copy constructor? + cookie->vTargets.push_back(*i); + cookie->pullSharedSequentialOffsets = &(*psi); + ulRelativeThreadNo = (iThread - cBaseThread) % i->GetThreadsPerFile(); + + PrintVerbose(profile.GetVerbose(), "thread %u is relative thread %u for %s\n", iThread, ulRelativeThreadNo, i->GetPath().c_str()); + break; + } + cBaseThread += i->GetThreadsPerFile(); + } + } + + cookie->pProfile = &profile; + cookie->pTimeSpan = &timeSpan; + cookie->hStartEvent = hStartEvent; + cookie->hEndEvent = hEndEvent; + cookie->ulThreadNo = iThread; + cookie->ulRelativeThreadNo = ulRelativeThreadNo; + cookie->pfAccountingOn = &fAccountingOn; + cookie->pullStartTime = &ullStartTime; + cookie->ulRandSeed = timeSpan.GetRandSeed() + iThread; // each thread has a different random seed + cookie->pRand = pRand; + + //Set thread group and proc affinity + + // Default: Round robin cpus in order of groups, starting at group 0. + // Fill each group before moving to next. + if (vAffinity.size() == 0) + { + cookie->wGroupNum = wGroupCtr; + cookie->bProcNum = bProcCtr; + + // advance to next active + g_SystemInformation.processorTopology.GetActiveGroupProcessor(wGroupCtr, bProcCtr, true); + } + // Assigned affinity. Round robin through the assignment list. + else + { + ULONG i = iThread % vAffinity.size(); + + cookie->wGroupNum = vAffinity[i].wGroup; + cookie->bProcNum = vAffinity[i].bProc; + } + + //create thread + cookie->pResults = &results.vThreadResults[iThread]; + + InterlockedIncrement(&g_lRunningThreadsCount); + DWORD dwThreadId; + HANDLE hThread = CreateThread(NULL, 64 * 1024, threadFunc, cookie, 0, &dwThreadId); + if (NULL == hThread) + { + //in case of error terminate running worker threads + PrintError("ERROR: unable to create thread (error code: %u)\n", GetLastError()); + InterlockedDecrement(&g_lRunningThreadsCount); + _AbortWorkerThreads(hStartEvent, vhThreads); + delete pRand; + delete cookie; + return false; + } + + //store handle to the thread + vhThreads[iThread] = hThread; + } + + if (STRUCT_SYNCHRONIZATION_SUPPORTS(pSynch, hStartEvent) && (NULL != pSynch->hStartEvent)) + { + if (WAIT_OBJECT_0 != WaitForSingleObject(pSynch->hStartEvent, INFINITE)) + { + PrintError("Error during WaitForSingleObject\n"); + _AbortWorkerThreads(hStartEvent, vhThreads); + return false; + } + } + + // + // get cycle count (it will be used to calculate actual work time) + // + DWORD dwWaitStatus = 0; + + //bAccountingOn = FALSE; // clear the accouning flag so that threads didn't count what they do while in the warmup phase + + BOOL bSynchStop = STRUCT_SYNCHRONIZATION_SUPPORTS(pSynch, hStopEvent) && (NULL != pSynch->hStopEvent); + BOOL bBreak = FALSE; + PEVENT_TRACE_PROPERTIES pETWSession = NULL; + + // + // send start signal + // + if (!SetEvent(hStartEvent)) + { + PrintError("Error signaling start event\n"); + // stopETW(bUseETW, hTraceSession); + _TerminateWorkerThreads(vhThreads); //FUTURE EXTENSION: timeout for worker threads + return false; + } + + // + // wait specified amount of time in each phase (warm up, test, cool down) + // + if (timeSpan.GetWarmup() > 0) + { + TraceLoggingActivity WarmActivity; + TraceLoggingWriteStart(WarmActivity, "Warm Up"); + PrintVerbose(profile.GetVerbose(), "starting warm up for %us...\n", timeSpan.GetWarmup()); + + if (bSynchStop) + { + assert(NULL != pSynch->hStopEvent); + dwWaitStatus = WaitForSingleObject(pSynch->hStopEvent, 1000 * timeSpan.GetWarmup()); + if (WAIT_OBJECT_0 != dwWaitStatus && WAIT_TIMEOUT != dwWaitStatus) + { + PrintError("Error during WaitForSingleObject\n"); + _TerminateWorkerThreads(vhThreads); + return false; + } + bBreak = (WAIT_TIMEOUT != dwWaitStatus); + } + else + { + Sleep(1000 * timeSpan.GetWarmup()); + } + + TraceLoggingWriteStop(WarmActivity, "Warm Up"); + } + + if (!bBreak) // proceed only if user didn't break the test + { + //FUTURE EXTENSION: starting ETW session shouldn't be done brutally here, should be done before warmup and here just a fast signal to start logging (see also stopping ETW session) + //FUTURE EXTENSION: put an ETW mark here, for easier parsing by external tools + + // + // start etw session + // + TRACEHANDLE hTraceSession = NULL; + if (fUseETW) + { + PrintVerbose(profile.GetVerbose(), "starting trace session\n"); + hTraceSession = StartETWSession(profile); + if (NULL == hTraceSession) + { + PrintError("Could not start ETW session\n"); + _TerminateWorkerThreads(vhThreads); + return false; + } + + if (NULL == CreateThread(NULL, 64 * 1024, etwThreadFunc, NULL, 0, NULL)) + { + PrintError("Warning: unable to create thread for ETW session\n"); + _TerminateWorkerThreads(vhThreads); + return false; + } + PrintVerbose(profile.GetVerbose(), "tracing events\n"); + } + + // + // notify the front-end that the test is about to start; + // do it before starting timing in order not to perturb measurements + // + if (STRUCT_SYNCHRONIZATION_SUPPORTS(pSynch, pfnCallbackTestStarted) && (NULL != pSynch->pfnCallbackTestStarted)) + { + pSynch->pfnCallbackTestStarted(); + } + + // + // read performance counters + // + if (_GetSystemPerfInfo(vPerfInit, profile.GetVerbose()) == FALSE) + { + PrintError("Error reading performance counters\n"); + _StopETW(fUseETW, hTraceSession); + _TerminateWorkerThreads(vhThreads); + return false; + } + + TraceLoggingActivity RunActivity; + TraceLoggingWriteStart(RunActivity, "Run Time"); + + PrintVerbose(profile.GetVerbose(), "starting measurements for %us...\n", timeSpan.GetDuration()); + + //get cycle count (it will be used to calculate actual work time) + ullStartTime = PerfTimer::GetTime(); + fAccountingOn = true; + + assert(timeSpan.GetDuration() > 0); + if (bSynchStop) + { + assert(NULL != pSynch->hStopEvent); + dwWaitStatus = WaitForSingleObject(pSynch->hStopEvent, 1000 * timeSpan.GetDuration()); + if (WAIT_OBJECT_0 != dwWaitStatus && WAIT_TIMEOUT != dwWaitStatus) + { + PrintError("Error during WaitForSingleObject\n"); + _StopETW(fUseETW, hTraceSession); + _TerminateWorkerThreads(vhThreads); //FUTURE EXTENSION: worker threads should have a chance to free allocated memory (see also other places calling terminateWorkerThreads()) + return FALSE; + } + bBreak = (WAIT_TIMEOUT != dwWaitStatus); + } + else + { + Sleep(1000 * timeSpan.GetDuration()); + } + + //get cycle count and perf counters + fAccountingOn = false; + ullTimeDiff = PerfTimer::GetTime() - ullStartTime; + PrintVerbose(profile.GetVerbose(), "stopped measurements, total measured time %.2lfs...\n", PerfTimer::PerfTimeToSeconds(ullTimeDiff)); + + TraceLoggingWriteStop(RunActivity, "Run Time"); + + if (_GetSystemPerfInfo(vPerfDone, profile.GetVerbose()) == FALSE) + { + PrintError("Error getting performance counters\n"); + _StopETW(fUseETW, hTraceSession); + _TerminateWorkerThreads(vhThreads); + return false; + } + + // + // notify the front-end that the test has just finished; + // do it after stopping timing in order not to perturb measurements + // + if (STRUCT_SYNCHRONIZATION_SUPPORTS(pSynch, pfnCallbackTestFinished) && (NULL != pSynch->pfnCallbackTestFinished)) + { + pSynch->pfnCallbackTestFinished(); + } + + // + // stop etw session + // + if (fUseETW) + { + PrintVerbose(profile.GetVerbose(), "stopping ETW session\n"); + pETWSession = StopETWSession(hTraceSession); + if (NULL == pETWSession) + { + PrintError("Error stopping ETW session\n"); + return false; + } + } + } + else + { + ullTimeDiff = 0; // mark that no test was run + } + + if ((timeSpan.GetCooldown() > 0) && !bBreak) + { + TraceLoggingActivity CoolActivity; + TraceLoggingWriteStart(CoolActivity, "Cool Down"); + PrintVerbose(profile.GetVerbose(), "starting cool down for %us...\n", timeSpan.GetCooldown()); + + if (bSynchStop) + { + assert(NULL != pSynch->hStopEvent); + dwWaitStatus = WaitForSingleObject(pSynch->hStopEvent, 1000 * timeSpan.GetCooldown()); + if (WAIT_OBJECT_0 != dwWaitStatus && WAIT_TIMEOUT != dwWaitStatus) + { + PrintError("Error during WaitForSingleObject\n"); + // stopETW(bUseETW, hTraceSession); + _TerminateWorkerThreads(vhThreads); + return false; + } + } + else + { + Sleep(1000 * timeSpan.GetCooldown()); + } + + TraceLoggingWriteStop(CoolActivity, "Cool Down"); + } + PrintVerbose(profile.GetVerbose(), "finished test...\n"); + + // + // signal the threads to finish + // + g_bRun = FALSE; + if (timeSpan.GetCompletionRoutines()) + { + if (!SetEvent(hEndEvent)) + { + PrintError("Error signaling end event\n"); + // stopETW(bUseETW, hTraceSession); + return false; + } + } + + // + // wait till all of the threads finish + // +#pragma warning( push ) +#pragma warning( disable : 28112 ) + while (g_lRunningThreadsCount > 0) + { + Sleep(10); //FUTURE EXTENSION: a timeout should be implemented + } +#pragma warning( pop ) + + + //check if there has been an error during threads execution + if (g_bThreadError) + { + PrintError("There has been an error during threads execution\n"); + return false; + } + + // + // close events' handles + // + CloseHandle(hStartEvent); + hStartEvent = NULL; + + if (NULL != hEndEvent) + { + CloseHandle(hEndEvent); + hEndEvent = NULL; + } + //FUTURE EXTENSION: hStartEvent and hEndEvent should be closed in case of error too + + // + // compute time spent by each cpu + // + for (DWORD p = 0; p < g_SystemInformation.processorTopology._ulProcessorCount; ++p) + { + assert(vPerfDone[p].IdleTime.QuadPart >= vPerfInit[p].IdleTime.QuadPart); + assert(vPerfDone[p].KernelTime.QuadPart >= vPerfInit[p].KernelTime.QuadPart); + assert(vPerfDone[p].UserTime.QuadPart >= vPerfInit[p].UserTime.QuadPart); + + vPerfDiff[p].IdleTime.QuadPart = vPerfDone[p].IdleTime.QuadPart - vPerfInit[p].IdleTime.QuadPart; + vPerfDiff[p].KernelTime.QuadPart = vPerfDone[p].KernelTime.QuadPart - vPerfInit[p].KernelTime.QuadPart; + vPerfDiff[p].UserTime.QuadPart = vPerfDone[p].UserTime.QuadPart - vPerfInit[p].UserTime.QuadPart; + + // + // Handle clock measurement jitter; if the difference is negative, set it to 0. This is usually seen + // as a -10000000 (full second of 100ns units) difference over very short runs. + // + // If the sum of kernel and user time is 0, treat it as a full idle with placeholder values. This provides + // a nonzero denominator for the CPU utilization calculation and avoids divide by zero -> INF results. + // Note that system clock convention is that kernel time includes idle time. + // + + if (vPerfDiff[p].IdleTime.QuadPart < 0) + { + PrintVerbose(profile.GetVerbose(), "time fixup: IdleTime < 0 @ %u : ticks %lld - %lld\n", p, vPerfDone[p].IdleTime.QuadPart, vPerfInit[p].IdleTime.QuadPart); + vPerfDiff[p].IdleTime.QuadPart = 0; + } + + if (vPerfDiff[p].KernelTime.QuadPart < 0) + { + PrintVerbose(profile.GetVerbose(), "time fixup: KernelTime < 0 @ %u : ticks %lld - %lld\n", p, vPerfDone[p].KernelTime.QuadPart, vPerfInit[p].KernelTime.QuadPart); + vPerfDiff[p].KernelTime.QuadPart = 0; + } + + if (vPerfDiff[p].UserTime.QuadPart < 0) + { + PrintVerbose(profile.GetVerbose(), "time fixup: UserTime < 0 @ %u : ticks %lld - %lld\n", p, vPerfDone[p].UserTime.QuadPart, vPerfInit[p].UserTime.QuadPart); + vPerfDiff[p].UserTime.QuadPart = 0; + } + + if (vPerfDiff[p].KernelTime.QuadPart + vPerfDiff[p].UserTime.QuadPart == 0) + { + PrintVerbose(profile.GetVerbose(), "time fixup: KernelTime+UserTime = 0 @ %u : ticks K (%lld - %lld) + U (%lld - %lld)\n", p, + vPerfDone[p].KernelTime.QuadPart, vPerfInit[p].KernelTime.QuadPart, + vPerfDone[p].UserTime.QuadPart, vPerfInit[p].UserTime.QuadPart); + + vPerfDiff[p].IdleTime.QuadPart = vPerfDiff[p].KernelTime.QuadPart = 1; + } + } + + // + // process results and pass them to the result parser + // + + // get processors perf. info + results.vSystemProcessorPerfInfo = vPerfDiff; + results.ullTimeCount = ullTimeDiff; + + // + // create structure containing etw results and properties + // + results.fUseETW = fUseETW; + if (fUseETW) + { + results.EtwEventCounters = g_EtwEventCounters; + results.EtwSessionInfo = _GetResultETWSession(pETWSession); + + // TODO: refactor to a separate function + results.EtwMask.bProcess = profile.GetEtwProcess(); + results.EtwMask.bThread = profile.GetEtwThread(); + results.EtwMask.bImageLoad = profile.GetEtwImageLoad(); + results.EtwMask.bDiskIO = profile.GetEtwDiskIO(); + results.EtwMask.bMemoryPageFaults = profile.GetEtwMemoryPageFaults(); + results.EtwMask.bMemoryHardFaults = profile.GetEtwMemoryHardFaults(); + results.EtwMask.bNetwork = profile.GetEtwNetwork(); + results.EtwMask.bRegistry = profile.GetEtwRegistry(); + results.EtwMask.bUsePagedMemory = profile.GetEtwUsePagedMemory(); + results.EtwMask.bUsePerfTimer = profile.GetEtwUsePerfTimer(); + results.EtwMask.bUseSystemTimer = profile.GetEtwUseSystemTimer(); + results.EtwMask.bUseCyclesCounter = profile.GetEtwUseCyclesCounter(); + + free(pETWSession); + } + + // free memory used by random data write buffers + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->FreeRandomDataWriteBuffer(); + } + + // TODO: this won't catch error cases, which exit early + InterlockedExchange(&g_lGeneratorRunning, 0); + return true; +} + +vector IORequestGenerator::_GetFilesToPrecreate(const Profile& profile) const +{ + vector vFilesToCreate; + const vector& vTimeSpans = profile.GetTimeSpans(); + map> filesMap; + for (const auto& timeSpan : vTimeSpans) + { + vector vTargets(timeSpan.GetTargets()); + for (const auto& target : vTargets) + { + struct CreateFileParameters createFileParameters; + createFileParameters.sPath = target.GetPath(); + createFileParameters.ullFileSize = target.GetFileSize(); + createFileParameters.fZeroWriteBuffers = target.GetZeroWriteBuffers(); + + filesMap[createFileParameters.sPath].push_back(createFileParameters); + } + } + + PrecreateFiles filter = profile.GetPrecreateFiles(); + for (auto fileMapEntry : filesMap) + { + if (fileMapEntry.second.size() > 0) + { + UINT64 ullLastNonZeroSize = fileMapEntry.second[0].ullFileSize; + UINT64 ullMaxSize = fileMapEntry.second[0].ullFileSize; + bool fLastZeroWriteBuffers = fileMapEntry.second[0].fZeroWriteBuffers; + bool fHasZeroSizes = false; + bool fConstantSize = true; + bool fConstantZeroWriteBuffers = true; + for (auto file : fileMapEntry.second) + { + ullMaxSize = max(ullMaxSize, file.ullFileSize); + if (ullLastNonZeroSize == 0) + { + ullLastNonZeroSize = file.ullFileSize; + } + if (file.ullFileSize == 0) + { + fHasZeroSizes = true; + } + if ((file.ullFileSize != 0) && (file.ullFileSize != ullLastNonZeroSize)) + { + fConstantSize = false; + } + if (file.fZeroWriteBuffers != fLastZeroWriteBuffers) + { + fConstantZeroWriteBuffers = false; + } + if (file.ullFileSize != 0) + { + ullLastNonZeroSize = file.ullFileSize; + } + fLastZeroWriteBuffers = file.fZeroWriteBuffers; + } + + if (fConstantZeroWriteBuffers && ullMaxSize > 0) + { + struct CreateFileParameters file = fileMapEntry.second[0]; + file.ullFileSize = ullMaxSize; + if (filter == PrecreateFiles::UseMaxSize) + { + vFilesToCreate.push_back(file); + } + else if ((filter == PrecreateFiles::OnlyFilesWithConstantSizes) && fConstantSize && !fHasZeroSizes) + { + vFilesToCreate.push_back(file); + } + else if ((filter == PrecreateFiles::OnlyFilesWithConstantOrZeroSizes) && fConstantSize) + { + vFilesToCreate.push_back(file); + } + } + } + } + + return vFilesToCreate; +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/IORequestGenerator/OverlappedQueue.cpp b/CristalDiskMark/source/diskspd22/IORequestGenerator/OverlappedQueue.cpp new file mode 100644 index 0000000..23fbd36 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/IORequestGenerator/OverlappedQueue.cpp @@ -0,0 +1,79 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "OverlappedQueue.h" +#include + +OverlappedQueue::OverlappedQueue(void) : + _pHead(nullptr), + _pTail(nullptr), + _cItems(0) +{ +} + +void OverlappedQueue::Add(OVERLAPPED *pOverlapped) +{ + pOverlapped->Internal = NULL; + if (_pHead == nullptr) + { + assert(_pTail == nullptr); + _pHead = pOverlapped; + } + else + { + assert(_pTail != nullptr); + _pTail->Internal = (ULONG_PTR)pOverlapped; + } + _pTail = pOverlapped; + _cItems++; +} + +bool OverlappedQueue::IsEmpty(void) const +{ + return (_pHead == nullptr); +} + +OVERLAPPED *OverlappedQueue::Remove(void) +{ + assert(!IsEmpty()); + + OVERLAPPED *pOverlapped = _pHead; + _pHead = (OVERLAPPED *)pOverlapped->Internal; + if (_pHead == nullptr) + { + _pTail = nullptr; + } + _cItems--; + return pOverlapped; +} + +size_t OverlappedQueue::GetCount() const +{ + return _cItems; +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/IORequestGenerator/ThroughputMeter.cpp b/CristalDiskMark/source/diskspd22/IORequestGenerator/ThroughputMeter.cpp new file mode 100644 index 0000000..726e740 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/IORequestGenerator/ThroughputMeter.cpp @@ -0,0 +1,143 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "Common.h" +#include "ThroughputMeter.h" + +ThroughputMeter::ThroughputMeter(void) : + _fRunning(false) +{ +} + +bool ThroughputMeter::IsRunning(void) const +{ + return _fRunning; +} + +void ThroughputMeter::Start(DWORD cBytesPerMillisecond, DWORD dwBlockSize, DWORD dwThinkTime, DWORD dwBurstSize) +{ + // Initialization + _cbCompleted = 0; + _cIO = 0; // number of completed IOs in the current burst + _cbBlockSize = dwBlockSize; + + _fThrottle = false; + _cBytesPerMillisecond = 0; + _fThink = false; + _ullDelayUntil = 0; + _thinkTime = 0; + _burstSize = 0; + _fRunning = false; + + _ullStartTimestamp = GetTickCount64(); + + if (0 != cBytesPerMillisecond) + { + _fThrottle = true; + _cBytesPerMillisecond = cBytesPerMillisecond; + _fRunning = true; + } + else if (0 != dwThinkTime) + { + _fThink = true; + _thinkTime = dwThinkTime; + _burstSize = dwBurstSize; + _fRunning = true; + } +} + +DWORD ThroughputMeter::GetSleepTime(void) const +{ + if (_fThink) + { + ULONGLONG ullTimestamp = GetTickCount64(); + if (ullTimestamp < _ullDelayUntil) + { + return (DWORD)(_ullDelayUntil - ullTimestamp); + } + else + { + return (_fThrottle) ? _GetThrottleTime() : 0; + } + } + else + { + if (_fThrottle) // think time has not been specified only check for throttling + { + return _GetThrottleTime(); + } + else + { + return 0; + } + } +} + +DWORD ThroughputMeter::_GetThrottleTime(void) const +{ + if ((g_ExperimentFlags & EXPERIMENT_TPUT_CALC) == 0) + { + ULONGLONG cbExpected = (GetTickCount64() - _ullStartTimestamp) * _cBytesPerMillisecond; + return cbExpected >= (_cbCompleted + _cbBlockSize) ? 0 : 1; + } + else + { + // prototype to calculate an actual sleep time + // under higher loads the ideal delay is likely in the microsecond range, but the minimum sleep is 1 ms + // however, at low rates it may be reasonable to calculate the delay and use it if > 1ms + + ULONGLONG elapsed = GetTickCount64() - _ullStartTimestamp; + ULONGLONG bytesNext = _cbCompleted + _cbBlockSize; + + if (elapsed * _cBytesPerMillisecond > bytesNext) + { + // below rate - no sleep + return 0; + } + + // above rate - sleep at least 1 ms + ULONGLONG sleepTarget = (bytesNext / _cBytesPerMillisecond) - elapsed; + + return max((DWORD)sleepTarget, 1); + } +} + +void ThroughputMeter::Adjust(size_t cb) +{ + _cbCompleted += cb; + _cIO++; + if (_fThink) + { + if (_cIO >= _burstSize) + { + _cIO = 0; + _ullDelayUntil = GetTickCount64() + _thinkTime; + } + } +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/IORequestGenerator/etw.cpp b/CristalDiskMark/source/diskspd22/IORequestGenerator/etw.cpp new file mode 100644 index 0000000..a85ff5b --- /dev/null +++ b/CristalDiskMark/source/diskspd22/IORequestGenerator/etw.cpp @@ -0,0 +1,498 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "etw.h" +#include "common.h" +#include +#include + +#include //WNODE_HEADER + +#define INITGUID //Include this #define to use SystemTraceControlGuid in Evntrace.h. +#include //ETW + +#include "IORequestGenerator.h" + +// variables declared in IORequestGenerator.cpp +extern struct ETWEventCounters g_EtwEventCounters; +extern BOOL volatile g_bTracing; + + +DEFINE_GUID ( /* 3d6fa8d4-fe05-11d0-9dda-00c04fd7ba7c */ + DiskIoGuid, + 0x3d6fa8d4, + 0xfe05, + 0x11d0, + 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c + ); + +DEFINE_GUID ( /* 90cbdc39-4a3e-11d1-84f4-0000f80464e3 */ + FileIoGuid, + 0x90cbdc39, + 0x4a3e, + 0x11d1, + 0x84, 0xf4, 0x00, 0x00, 0xf8, 0x04, 0x64, 0xe3 + ); + +DEFINE_GUID ( /* 2cb15d1d-5fc1-11d2-abe1-00a0c911f518 */ + ImageLoadGuid, + 0x2cb15d1d, + 0x5fc1, + 0x11d2, + 0xab, 0xe1, 0x00, 0xa0, 0xc9, 0x11, 0xf5, 0x18 + ); + +DEFINE_GUID ( /* 3d6fa8d3-fe05-11d0-9dda-00c04fd7ba7c */ + PageFaultGuid, + 0x3d6fa8d3, + 0xfe05, + 0x11d0, + 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c + ); + +DEFINE_GUID ( /* 9a280ac0-c8e0-11d1-84e2-00c04fb998a2 */ + TcpIpGuid, + 0x9a280ac0, + 0xc8e0, + 0x11d1, + 0x84, 0xe2, 0x00, 0xc0, 0x4f, 0xb9, 0x98, 0xa2 + ); + +DEFINE_GUID ( /* bf3a50c5-a9c9-4988-a005-2df0b7c80f80 */ + UdpIpGuid, + 0xbf3a50c5, + 0xa9c9, + 0x4988, + 0xa0, 0x05, 0x2d, 0xf0, 0xb7, 0xc8, 0x0f, 0x80 + ); + +DEFINE_GUID ( /* 3d6fa8d0-fe05-11d0-9dda-00c04fd7ba7c */ + ProcessGuid, + 0x3d6fa8d0, + 0xfe05, + 0x11d0, + 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c + ); + +DEFINE_GUID ( /* AE53722E-C863-11d2-8659-00C04FA321A1 */ + RegistryGuid, + 0xae53722e, + 0xc863, + 0x11d2, + 0x86, 0x59, 0x0, 0xc0, 0x4f, 0xa3, 0x21, 0xa1 +); + +DEFINE_GUID ( /* 3d6fa8d1-fe05-11d0-9dda-00c04fd7ba7c */ + ThreadGuid, + 0x3d6fa8d1, + 0xfe05, + 0x11d0, + 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c + ); + +/*****************************************************************************/ +// consumes events' data +// +BOOL TraceEvents() +{ + TRACEHANDLE handles[1]; + EVENT_TRACE_LOGFILE logfile; + + memset(&logfile, 0, sizeof(EVENT_TRACE_LOGFILE)); + + logfile.LoggerName = KERNEL_LOGGER_NAME; + logfile.LogFileName = NULL; + logfile.LogFileMode = EVENT_TRACE_REAL_TIME_MODE; + + logfile.IsKernelTrace = true; + + handles[0] = OpenTrace(&logfile); + if( (TRACEHANDLE)INVALID_HANDLE_VALUE == handles[0] ) + { + PrintError("ETW ERROR: OpenTrace failed (error code: %d)\n", GetLastError()); + return false; + } + else + { + ProcessTrace(handles, 1, 0, 0); + CloseTrace(handles[0]); + } + + return true; +} + +/*****************************************************************************/ +// allocates memory for ETW properties structure +// +PEVENT_TRACE_PROPERTIES allocateEventTraceProperties() +{ + PEVENT_TRACE_PROPERTIES pProperties = NULL; + size_t size = 0; + + + size = sizeof(EVENT_TRACE_PROPERTIES)+sizeof(KERNEL_LOGGER_NAME); + pProperties = (PEVENT_TRACE_PROPERTIES)malloc(size); + if( NULL == pProperties ) + { + PrintError("FATAL ERROR: unable to allocate memory (error code: %d)\n", GetLastError()); + return NULL; + } + + memset(pProperties, 0, size); + + pProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); + pProperties->Wnode.BufferSize = (ULONG)size; + pProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID; + + strcpy_s((char *)pProperties+pProperties->LoggerNameOffset, + size-pProperties->LoggerNameOffset, KERNEL_LOGGER_NAME); + + return pProperties; +} + +/*****************************************************************************/ +// callback function for disk I/O events +void WINAPI eventDiskIo(PEVENT_TRACE pEvent) +{ + if( EVENT_TRACE_TYPE_IO_READ == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullIORead; + } + else if( EVENT_TRACE_TYPE_IO_WRITE == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullIOWrite; + } +} + +/*****************************************************************************/ +// callback function for LoadImage events +// +void WINAPI eventLoadImage(PEVENT_TRACE pEvent) +{ + if( EVENT_TRACE_TYPE_LOAD == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullImageLoad; + } +} + +/*****************************************************************************/ +// callback function for memory events +// +void WINAPI eventPageFault(PEVENT_TRACE pEvent) +{ + if( EVENT_TRACE_TYPE_MM_COW == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullMMCopyOnWrite; + } + else if( EVENT_TRACE_TYPE_MM_DZF == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullMMDemandZeroFault; + } + else if( EVENT_TRACE_TYPE_MM_GPF == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullMMGuardPageFault; + } + else if( EVENT_TRACE_TYPE_MM_HPF == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullMMHardPageFault; + } + else if( EVENT_TRACE_TYPE_MM_TF == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullMMTransitionFault; + } +} + +/*****************************************************************************/ +// callback function for network and TCP/IP events +// +void WINAPI eventTcpIp(PEVENT_TRACE pEvent) +{ + if( EVENT_TRACE_TYPE_ACCEPT == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullNetAccept; + } + else if( EVENT_TRACE_TYPE_CONNECT == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullNetConnect; + } + else if( EVENT_TRACE_TYPE_DISCONNECT == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullNetDisconnect; + } + else if( EVENT_TRACE_TYPE_RECEIVE == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullNetTcpReceive; + } + else if( EVENT_TRACE_TYPE_RECONNECT == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullNetReconnect; + } + else if( EVENT_TRACE_TYPE_RETRANSMIT == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullNetRetransmit; + } + else if( EVENT_TRACE_TYPE_SEND == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullNetTcpSend; + } +} + +/*****************************************************************************/ +// callback function for UDP/IP events +// +void WINAPI eventUdpIp(PEVENT_TRACE pEvent) +{ + if( EVENT_TRACE_TYPE_SEND == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullNetUdpSend; + } + else if( EVENT_TRACE_TYPE_RECEIVE == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullNetUdpReceive; + } +} + +/*****************************************************************************/ +// callback function for thread events +// +void WINAPI eventThread(PEVENT_TRACE pEvent) +{ + if( EVENT_TRACE_TYPE_START == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullThreadStart; + } + else if( EVENT_TRACE_TYPE_END == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullThreadEnd; + } +} + +/*****************************************************************************/ +// callback function for process events +// +void WINAPI eventProcess(PEVENT_TRACE pEvent) +{ + if( EVENT_TRACE_TYPE_START == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullProcessStart; + } + else if( EVENT_TRACE_TYPE_END == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullProcessEnd; + } +} + +/*****************************************************************************/ +// callback function for registry events +// +void WINAPI eventRegistry(PEVENT_TRACE pEvent) +{ + if( EVENT_TRACE_TYPE_REGCREATE == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegCreate; + } + else if( EVENT_TRACE_TYPE_REGDELETE == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegDelete; + } + else if( EVENT_TRACE_TYPE_REGDELETEVALUE == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegDeleteValue; + } + else if( EVENT_TRACE_TYPE_REGENUMERATEKEY == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegEnumerateKey; + } + else if( EVENT_TRACE_TYPE_REGENUMERATEVALUEKEY == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegEnumerateValueKey; + } + else if( EVENT_TRACE_TYPE_REGFLUSH == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegFlush; + } + else if( EVENT_TRACE_TYPE_REGOPEN == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegOpen; + } + else if( EVENT_TRACE_TYPE_REGQUERY == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegQuery; + } + else if( EVENT_TRACE_TYPE_REGQUERYMULTIPLEVALUE == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegQueryMultipleValue; + } + else if( EVENT_TRACE_TYPE_REGQUERYVALUE == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegQueryValue; + } + else if( EVENT_TRACE_TYPE_REGSETINFORMATION == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegSetInformation; + } + else if( EVENT_TRACE_TYPE_REGSETVALUE == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegSetValue; + } +} + +/*****************************************************************************/ +// starts ETW session and sets callback functions for various groups of events +// +TRACEHANDLE StartETWSession(const Profile& profile) +{ + PEVENT_TRACE_PROPERTIES pProperties; + + pProperties = allocateEventTraceProperties(); + if (nullptr == pProperties) + { + return 0; + } + + pProperties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; + + //use paged memory + if (profile.GetEtwUsePagedMemory()) + { + pProperties->LogFileMode |= EVENT_TRACE_USE_PAGED_MEMORY; + } + + // + // event classes + // + if (profile.GetEtwProcess()) + { + pProperties->EnableFlags |= EVENT_TRACE_FLAG_PROCESS; + SetTraceCallback(&ProcessGuid, eventProcess); + } + + if (profile.GetEtwThread()) + { + pProperties->EnableFlags |= EVENT_TRACE_FLAG_THREAD; + SetTraceCallback(&ThreadGuid, eventThread); + } + + if (profile.GetEtwImageLoad()) + { + pProperties->EnableFlags |= EVENT_TRACE_FLAG_IMAGE_LOAD; + SetTraceCallback(&ImageLoadGuid, eventLoadImage); + } + + if (profile.GetEtwDiskIO()) + { + pProperties->EnableFlags |= EVENT_TRACE_FLAG_DISK_IO; + SetTraceCallback(&DiskIoGuid, eventDiskIo); + } + + if (profile.GetEtwMemoryPageFaults()) + { + pProperties->EnableFlags |= EVENT_TRACE_FLAG_MEMORY_PAGE_FAULTS; + SetTraceCallback(&PageFaultGuid, eventPageFault); + } + + if (profile.GetEtwMemoryHardFaults()) + { + pProperties->EnableFlags |= EVENT_TRACE_FLAG_MEMORY_HARD_FAULTS; + SetTraceCallback(&PageFaultGuid, eventPageFault); + } + + if (profile.GetEtwNetwork()) + { + pProperties->EnableFlags |= EVENT_TRACE_FLAG_NETWORK_TCPIP; + SetTraceCallback(&TcpIpGuid, eventTcpIp); + SetTraceCallback(&UdpIpGuid, eventUdpIp); + } + + if (profile.GetEtwRegistry()) + { + pProperties->EnableFlags |= EVENT_TRACE_FLAG_REGISTRY; + SetTraceCallback(&RegistryGuid, eventRegistry); + } + + // + // timer + // + if (profile.GetEtwUsePerfTimer()) + { + pProperties->Wnode.ClientContext = 1; + } + if (profile.GetEtwUseSystemTimer()) + { + pProperties->Wnode.ClientContext = 2; + } + if (profile.GetEtwUseCyclesCounter()) + { + pProperties->Wnode.ClientContext = 3; + } + + pProperties->Wnode.Guid = SystemTraceControlGuid; + + TRACEHANDLE hTraceSession; + ULONG ret = StartTrace(&hTraceSession, KERNEL_LOGGER_NAME, pProperties); + free(pProperties); + if (ERROR_SUCCESS != ret) + { + PrintError("Error starting trace session\n"); + return 0; + } + + return hTraceSession; +} + +/*****************************************************************************/ +// stops ETW session +// +PEVENT_TRACE_PROPERTIES StopETWSession(TRACEHANDLE hTraceSession) +{ + PEVENT_TRACE_PROPERTIES pProperties; + + pProperties = allocateEventTraceProperties(); + if( NULL == pProperties ) + { + return NULL; + } + + ULONG ret; + + ret = ControlTrace(hTraceSession, NULL, pProperties, EVENT_TRACE_CONTROL_STOP); + if( ERROR_SUCCESS != ret ) + { + PrintError("Error stopping trace session\n"); + return NULL; + } + + //wait + while(g_bTracing) + { + Sleep(10); // TODO: remove active waiting + } + + return pProperties; +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/LICENSE b/CristalDiskMark/source/diskspd22/LICENSE new file mode 100644 index 0000000..cd7af07 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/CristalDiskMark/source/diskspd22/Process-DiskSpd.ps1 b/CristalDiskMark/source/diskspd22/Process-DiskSpd.ps1 new file mode 100644 index 0000000..be7cb6f --- /dev/null +++ b/CristalDiskMark/source/diskspd22/Process-DiskSpd.ps1 @@ -0,0 +1,228 @@ +<# +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +#> + +param( + [string] $xmlresultpath = ".", + [string] $outfile = "result.tsv" + ) + +function Log-Host( + $ForegroundColor = 'Green', + [parameter(Position=0, ValueFromRemainingArguments=$true)] $args + ) +{ + write-host -ForegroundColor $ForegroundColor $(get-date -DisplayHint Time) $args +} + +function get-latency( $x ) { + + $x.Results.TimeSpan.Latency.Bucket |% { + $_.Percentile,$_.ReadMilliseconds,$_.WriteMilliseconds -join "`t" + } +} + +function process-result( + ) +{ + PROCESS { + + log-host -fore green $_ + + if ((Get-Content $_ -first 1) -ne '') { + + write-host -ForegroundColor Red ERROR: $_ does not appear to be an DiskSpd XML result. Make sure `-Rxml was specified. + + } else { + + # count valid results + $script:valid += 1 + + $x = [xml](Get-Content $_) + + $lf = $_.fullname -replace '.xml','.lat.tsv' + + if (-not [io.file]::Exists($lf)) { + get-latency $x > $lf + } + + $system = $x.Results.System.ComputerName + $t = $x.Results.TimeSpan.TestTimeSeconds + + # sum read and write iops across all threads and targets + $ri = ($x.Results.TimeSpan.Thread.Target | + measure -sum -Property ReadCount).Sum + $wi = ($x.Results.TimeSpan.Thread.Target | + measure -sum -Property WriteCount).Sum + $rb = ($x.Results.TimeSpan.Thread.Target | + measure -sum -Property ReadBytes).Sum + $wb = ($x.Results.TimeSpan.Thread.Target | + measure -sum -Property WriteBytes).Sum + + $riops = $ri / $t + $wiops = $wi / $t + $rbw = $rb / $t + $wbw = $wb / $t + + # avoid undefined/div-by-zero when calculating coefficient of var + $ricv = $null + if ($riops -gt 0) { $ricv = $x.Results.TimeSpan.Iops.ReadIopsStdDev / $riops } + $wicv = $null + if ($wiops -gt 0) { $wicv = $x.Results.TimeSpan.Iops.WriteIopsStdDev / $wiops } + + if ($ricv -gt 0.10 -or $wicv -gt 0.10) { + log-host -ForegroundColor red ("ricv {0:F3} wicv {1:F3}" -f $ricv,$wicv) + } else { + log-host ("ricv {0:F3} wicv {1:F3}" -f $ricv,$wicv) + } + + # note that with runs specified on the command line, only a single write ratio, + # outstanding request count and blocksize can be specified, so sampling the one + # used for the first thread is sufficient. + # + # this script DOES NOT handle complex timespan specifications + + if (($x.Results.Profile.TimeSpans.TimeSpan.Targets.Target.Random | select -first 1) -gt 0) { + $pat = "Random" + $align = ($x.Results.Profile.TimeSpans.TimeSpan.Targets.Target.Random | select -first 1) + } else { + $pat = "Sequential" + $align = ($x.Results.Profile.TimeSpans.TimeSpan.Targets.Target.StrideSize | select -first 1) + } + + # convert to object form / export-csv + + $o = @{ + 'ResultFileName' = $_.BaseName; + 'Path' = ($x.Results.TimeSpan.Thread.target.path | select -first 1); + 'Affinity' = ($x.Results.Profile.TimeSpans.TimeSpan.DisableAffinity -ne 'true'); + 'System' = $system; + 'TestTime' = $t; + 'WriteRatio' = ($x.Results.Profile.TimeSpans.TimeSpan.Targets.Target.WriteRatio | + select -first 1); + 'Pattern' = $pat; + 'Align' = $align; + 'Threads' = $x.Results.TimeSpan.ThreadCount; + 'Outstanding' = ($x.Results.Profile.TimeSpans.TimeSpan.Targets.Target.RequestCount | + select -first 1); + 'Block' = ($x.Results.Profile.TimeSpans.TimeSpan.Targets.Target.BlockSize | + select -first 1); + 'CPU' = $x.Results.TimeSpan.CpuUtilization.Average.UsagePercent; + 'CPUKern' = $x.Results.TimeSpan.CpuUtilization.Average.KernelPercent; + 'CPUHotCore' = ($x.Results.TimeSpan.CpuUtilization.CPU.UsagePercent |% { [decimal] $_ } | + sort -Descending | select -first 1); + 'CPUHotCoreKern' = ($x.Results.TimeSpan.CpuUtilization.CPU.KernelPercent |% { [decimal] $_ } | + sort -Descending | select -first 1); + 'ReadIOPS' = $riops; + 'ReadIOPSCoV' = $ricv; + 'ReadBW' = $rbw; + 'ReadBytes' = $rb; + 'LatReadAV' = $x.Results.TimeSpan.Latency.AverageReadMilliseconds; + 'WriteIOPS' = $wiops; + 'WriteIOPSCoV' = $wicv; + 'WriteBW' = $wbw; + 'WriteBytes' = $wb; + 'LatWriteAV' = $x.Results.TimeSpan.Latency.AverageWriteMilliseconds; + 'TotIOPS' = $riops + $wiops; + 'TotBW' = $rbw + $wbw; + } + + # extract the subset of latency percentiles as specified above in $l + $h = @{}; $x.Results.TimeSpan.Latency.Bucket |% { $h[$_.Percentile] = $_ } + + $ls = $script:l |% { + $b = $h[$_]; + if ($b.ReadMilliseconds) { $v = $b.ReadMilliseconds } else { $v = "" } + $o["LatRead$_"] = $v + if ($b.WriteMilliseconds) { $v = $b.WriteMilliseconds } else { $v = "" } + $o["LatWrite$_"] = $v + } + + # Parse additional fields from filename? other? here + $add = @() + if ($_.BaseName -match "-wc(\d+)wicpw(\d+)vm(\d+)") { + $add = $matches[1..3] + } elseif ($_.BaseName -match "-basevm(\d+)") { + $add = @('base','base',$matches[1]) + } else { + $add = @('na','na','na') + } + + if ($add.count -gt 0) { + write-host adding fields ($add -join ' : ') + } + + # Place any addons. user to rename manually for the moment. + $n = 1 + $add |% { + $o["Add$n"] = $_ + $n++ + } + + # emit for export + $o + } + } +} + +######################### +# +$delim = "`t" +$l = @(); foreach ($i in 25,50,75,90,95,99,99.9,99.99,99.999,100) { $l += ,[string]$i } + +$files = dir (join-path $xmlresultpath *.xml) + +######################### +# get schema and process/emit column headers +$valid = 0 +$scratch = $files[0] | process-result + +# if no valid results found, stop now +if ($valid -eq 0) { + + write-host -ForegroundColor Red ERROR: `"$xmlresultpath`"` may not contain DISKSPD result files + return +} + +# lexically order the column schema and emit with bounding quotes per field +$fields = $scratch.Keys | sort + +# column headers to output +$($fields |% { "`"$_`"" }) -join "$delim" | Out-File -FilePath $outfile + +######################### +# now process all files for rows +$valid = 0 +$files | process-result |% { + $row = $_ + $($fields |% { "`"$($row[$_])`"" }) -join "$delim" +} | Out-File -FilePath $outfile -Append + +# if some invalid files were found, warn +if ($valid -ne $files.count) { + + write-host -ForegroundColor Red WARNING: `"$xmlresultpath`"` contained $($files.count - $valid) non-DISKSPD xml files + return +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/README.md b/CristalDiskMark/source/diskspd22/README.md new file mode 100644 index 0000000..782f9b3 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/README.md @@ -0,0 +1,150 @@ +DiskSpd +======= + +DiskSpd is a storage performance tool from the Windows, Windows Server and Cloud Server Infrastructure engineering teams at Microsoft. Please visit for updated documentation. + +In addition to the tool itself, this repository hosts measurement frameworks which utilize DiskSpd. The initial example is [VM Fleet](https://github.com/Microsoft/diskspd/blob/master/Frameworks/VMFleet), used for Windows Server Hyper-Converged environments with Storage Spaces Direct. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +Releases +======== + +The [Releases](https://github.com/Microsoft/diskspd/releases) page includes **pre-compiled binaries (ZIP) and source code** for the most current releases of the DiskSpd tool. The latest update to DiskSpd can always be downloaded from (aka ). + +What's New? +=========== + +## DISKSPD + +# DISKSPD 2.2 6/3/2024 + +**NOTE:** changes to the asynchronous IO loop will require rebaselining results with queue depths greater than 1. +The new design drains the completion queue more aggressively, shrinking delays that impacted latency measurement +especially on faster storage targeting higher queue depths. Latency measurement is improved at a very small cost +to rates, as well as improving rates when latency measurement is not used (`-D` or `-L`) due to batch dequeue. + +Smaller IO sizes will see the most change. + +* New: Socket, NUMA, Core and Power Efficiency Class (big/little cores) added to processor topology reporting (XML and text output) + * topology elements only displayed in text results when > 1 are present (e.g. multi-socket systems) + * CPU numbering remains group relative, as is the new Core numbering + * highest Power Efficiency Class is marked with a `P` suffix (this will usually be `1P` v. `0`) + * **NOTE**: efficiency classes can have major impact; work on heterogenous systems **must** be aware of core properties in combination with thread affinity rules (see `-a` and `-n `) +* New: active power scheme reporting +* New: base target offset switch now allows range specification: `-Bbase[:length]`; replaces `-B` and `-f` +* post-run latency histogram processing now significantly faster +* verbose output is more consistent; includes actual warmup, measured and cooldown intervals v. expected + +Binary release supports down to Windows 8 and Windows Server 2012; now uses the Universal CRT. + +# DISKSPD 2.1 7/1/2021 + +* New `-gi` form allowing throughput limit specification in units of IOPS (per specified blocksize) +* New `-rs` to specify mixed random/sequential operation (pct random); geometric distribution of run lengths +* New `-rd` to specify non-uniform IO distributions across target + * `pct` by target percentage + * `abs` by absolute offset +* New `-Rp` to show specified parameter set in indicated profile output form; works with -X XML profiles and conventional command line +* XML results/profiles are now indented for ease of review +* Text result output updates + * now shows values in size units (K/M/G, and now TiB) to two decimals + * thread stride no longer shown unless specified + * -F/-O threadpool parameters shown +* XML profiles can now be built more generically + * XML profiles can be stated in terms of templated target names (*1, *2), replaced in order from command line invocation + * the command line now allows options alongside -X: -v, -z, -R and -W/-d/-C along with template target specs + +# DISKSPD 2.0.21a 9/21/2018 + +* Added support for memory mapped I/O: + * New `-Sm` option to enable memory mapped I/O + * New `-N` option to specify flush options for memory mapped I/O +* Added support for providing Event Tracing for Windows (ETW) events +* Included a Windows Performance Recorder (WPR) profile to enable ETW tracing +* Added system information to the ResultParser output + +# DISKSPD 2.0.20a 2/28/2018 + +* Changes that may require rebaselining of results: + * New random number generator that may show an observable decreased cost + * Switched to 512-byte aligned buffers with the `-Z` option to increase performance +* New `-O` option for specifying the number of outstanding IO requests per thread +* New `-Zr` option for per-IO randomization of write buffer content +* XML: Adds a new `` element to support target weighting schemes +* Enhanced statistics captured from IOPS data +* Added support for validating XML profiles using an in-built XSD +* Added support for handling RAW volumes +* Updated CPU statistics to work on > 64-core systems +* Updated calculation and accuracy of CPU statistics +* Re-enable support for ETW statistics + +# DISKSPD 2.0.18a 5/31/2016 + +* update `/?` example to use `-Sh` v. deprecated `-h` +* fix operation on volumes on GPT partitioned media (:) +* fix IO priority hint to proper stack alignment (if not 8 byte, will fail) +* use iB notation to clarify that text result output is in 2^n units (KiB/MiB/GiB) + +# DISKSPD 2.0.17a 5/01/2016 + +* `-S` is expanded to control write-through independent of OS/software cache. Among other things, this allows buffered write-through to be specified (`-Sbw`). +* XML: adds a new `` element to specify write-through +* XML: `` is no longer emitted (still parsed, though), in favor or `` and `` +* Text output: OS/software cache and write-through state are now documented separately (adjacent lines) +* Latency histogram now reports to 9-nines (one part in one billion) in both text and XML output +* Error message added for failure to open write-content source file (`-Z,`) + +## VM Fleet + +VM Fleet is a performance characterization and analyst framework for exploring the storage capabilities of Windows Server Hyper-Converged environments with Storage Spaces Direct. + +# VM Fleet 2.1.0.0 4/3/2024 + +* Support for Arc VM management (only applicable to clusters managed by Arc) +* `Set-FleetRunProfileScript` - produce a free-run script based on one of the defined workload profiles +* `Watch-FleetCPU` - new support for monitoring guest VCPU utilization (-Guest); can handle data outages +* Fix: performance counter handling now manages intermittent data dropouts (per conventional relog.exe) +* Fix: mid-run vm health check now handles the possibility of many vms taking longer than intended runtime to validate; early exit to avoid false failures +* Fix: ignore reboot required indication from cache layer when changing cache behavior; avoid false failure + +# VM Fleet 2.0.2.2 12/1/2021 + +* Fix cluster remoting issue during New-Fleet caused by 2.0.2.1 work +* Use timestamped logging in New-Fleet, simplify and de-colorize default output + +# VM Fleet 2.0.2.1 11/9/2021 + +* Fix cluster remoting issues in Move-Fleet and Get-FleetDataDiskEstimate +* Fix timing issue with Start-FleetSweep; always start from fleet pause to avoid triggering free run +* Use uniqueness to guarantee Start-FleetSweep runs profile in case of repeat + +# VM Fleet 2.0.2 11/2/2021 + +* Windows Server 2019/RS5 host operation now confirmed & supported +* Read cache warmup for HDD capacity systems should now be faster + +`Set-FleetPause` will wait for VM responses before completion by default (see -Timeout) + +Several minor fixes including: + +* Disable Windows Recovery Console in fleet VMs +* Fix: `Show-Fleet` IOPS view now aggregates all VM disk devices +* Fix: clean up leaked/conflicting data collectors and blg automatically + +# VM Fleet 2.0 9/22/2021 + +* major release and rewrite as a first class Powershell module +* original script-based VM Fleet remains available at Frameworks/VMFleet1.0 +* see the [documentation](https://github.com/microsoft/diskspd/wiki/VMFleet) in the Wiki + +Source Code +=========== + +The source code for DiskSpd is hosted on GitHub at: + + + +Any issues with DiskSpd can be reported using the following link: + + diff --git a/CristalDiskMark/source/diskspd22/ResultParser/ResultParser.cpp b/CristalDiskMark/source/diskspd22/ResultParser/ResultParser.cpp new file mode 100644 index 0000000..8eca72b --- /dev/null +++ b/CristalDiskMark/source/diskspd22/ResultParser/ResultParser.cpp @@ -0,0 +1,1334 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +// ResultParser.cpp : Defines the entry point for the DLL application. +// +#include "ResultParser.h" + +#include "common.h" +#include + +#include +#include +#include //ntdll.dll + +#include //WNODE_HEADER +#include + +#include + +// TODO: refactor to a single function shared with the XmlResultParser +// Note: not thread safe (avoid 4K on the stack) + +static char printBuffer[4096] = {}; + +/// for CrystalDiskMark +int ResultParser::GetTotalScore() +{ + return _totalScore; +} + +double ResultParser::GetAverageLatency() +{ + return _averageLatency; +} + +void ResultParser::_Print(const char *format, ...) +{ + assert(nullptr != format); + va_list listArg; + va_start(listArg, format); + vsprintf_s(printBuffer, _countof(printBuffer), format, listArg); + va_end(listArg); + _sResult += printBuffer; +} + +/*****************************************************************************/ +// display file size in a user-friendly form +// + +struct { + UINT32 sizeShift; + PCHAR name; +} sizeUnits[] = { + { 40, "TiB" }, + { 30, "GiB" }, + { 20, "MiB" }, + { 10, "KiB" } +}; + +void ResultParser::_DisplayFileSize(UINT64 fsize, UINT32 align) +{ + char fmtbuf[16]; + + for (auto& s : sizeUnits) + { + UINT64 sz = (UINT64)1 << s.sizeShift; + if (fsize >= sz) + { + // Even multiple? + if ((fsize & (sz - 1)) == 0) + { + // note: guaranteed no loss of precision - TB shift guarantees + // 0 in high 32bits. + UINT32 f = static_cast(fsize >> s.sizeShift); + + if (align) + { + // "%u%s" + _snprintf_s(fmtbuf, sizeof(fmtbuf), "%%%uu%%s", align); + _Print(fmtbuf, f, s.name); + } + else + { + _Print("%u%s", f, s.name); + } + return; + } + + // Not even, use fp. + double f = static_cast(fsize) / sz; + + if (align) + { + // "%.2f%s" + _snprintf_s(fmtbuf, sizeof(fmtbuf), "%%%u.2f%%s", align); + _Print(fmtbuf, f, s.name); + } + else + { + _Print("%0.2f%s", f, s.name); + } + return; + } + } + + _Print("%I64u", fsize); +} + +/*****************************************************************************/ +void ResultParser::_DisplayETWSessionInfo(struct ETWSessionInfo sessionInfo) +{ + _Print("\n\n"); + _Print(" ETW Buffer Settings & Statistics\n"); + _Print("--------------------------------------------------------\n"); + _Print("(KB) Buffers (Secs) (Mins)\n"); + _Print("Size | Min | Max | Free | Written | Flush Age\n"); + + _Print("%-5lu %5lu %-5lu %-2lu %8lu %8lu %8d\n\n", + sessionInfo.ulBufferSize, + sessionInfo.ulMinimumBuffers, + sessionInfo.ulMaximumBuffers, + sessionInfo.ulFreeBuffers, + sessionInfo.ulBuffersWritten, + sessionInfo.ulFlushTimer, + sessionInfo.lAgeLimit); + + _Print("Allocated Buffers:\t%lu\n", + sessionInfo.ulNumberOfBuffers); + + _Print("Lost Events:\t\t%lu\n", + sessionInfo.ulEventsLost); + + _Print("Lost Log Buffers:\t%lu\n", + sessionInfo.ulLogBuffersLost); + + _Print("Lost Real Time Buffers:\t%lu\n", + sessionInfo.ulRealTimeBuffersLost); +} + +/*****************************************************************************/ +void ResultParser::_DisplayETW(struct ETWMask ETWMask, struct ETWEventCounters EtwEventCounters) +{ + _Print("\n\n\nETW:\n"); + _Print("----\n\n"); + + if (ETWMask.bDiskIO) + { + _Print("\tDisk I/O\n"); + + _Print("\t\tRead: %I64u\n", EtwEventCounters.ullIORead); + _Print("\t\tWrite: %I64u\n", EtwEventCounters.ullIOWrite); + } + if (ETWMask.bImageLoad) + { + _Print("\tLoad Image\n"); + + _Print("\t\tLoad Image: %I64u\n", EtwEventCounters.ullImageLoad); + } + if (ETWMask.bMemoryPageFaults) + { + _Print("\tMemory Page Faults\n"); + + _Print("\t\tCopy on Write: %I64u\n", EtwEventCounters.ullMMCopyOnWrite); + _Print("\t\tDemand Zero fault: %I64u\n", EtwEventCounters.ullMMDemandZeroFault); + _Print("\t\tGuard Page fault: %I64u\n", EtwEventCounters.ullMMGuardPageFault); + _Print("\t\tHard page fault: %I64u\n", EtwEventCounters.ullMMHardPageFault); + _Print("\t\tTransition fault: %I64u\n", EtwEventCounters.ullMMTransitionFault); + } + if (ETWMask.bMemoryHardFaults && !ETWMask.bMemoryPageFaults ) + { + _Print("\tMemory Hard Faults\n"); + _Print("\t\tHard page fault: %I64u\n", EtwEventCounters.ullMMHardPageFault); + } + if (ETWMask.bNetwork) + { + _Print("\tNetwork\n"); + + _Print("\t\tAccept: %I64u\n", EtwEventCounters.ullNetAccept); + _Print("\t\tConnect: %I64u\n", EtwEventCounters.ullNetConnect); + _Print("\t\tDisconnect: %I64u\n", EtwEventCounters.ullNetDisconnect); + _Print("\t\tReconnect: %I64u\n", EtwEventCounters.ullNetReconnect); + _Print("\t\tRetransmit: %I64u\n", EtwEventCounters.ullNetRetransmit); + _Print("\t\tTCP/IP Send: %I64u\n", EtwEventCounters.ullNetTcpSend); + _Print("\t\tTCP/IP Receive: %I64u\n", EtwEventCounters.ullNetTcpReceive); + _Print("\t\tUDP/IP Send: %I64u\n", EtwEventCounters.ullNetUdpSend); + _Print("\t\tUDP/IP Receive: %I64u\n", EtwEventCounters.ullNetUdpReceive); + } + if (ETWMask.bProcess) + { + _Print("\tProcess\n"); + + _Print("\t\tStart: %I64u\n", EtwEventCounters.ullProcessStart); + _Print("\t\tEnd: %I64u\n", EtwEventCounters.ullProcessEnd); + } + if (ETWMask.bRegistry) + { + _Print("\tRegistry\n"); + + _Print("\t\tNtCreateKey: %I64u\n", + EtwEventCounters.ullRegCreate); + + _Print("\t\tNtDeleteKey: %I64u\n", + EtwEventCounters.ullRegDelete); + + _Print("\t\tNtDeleteValueKey: %I64u\n", + EtwEventCounters.ullRegDeleteValue); + + _Print("\t\tNtEnumerateKey: %I64u\n", + EtwEventCounters.ullRegEnumerateKey); + + _Print("\t\tNtEnumerateValueKey: %I64u\n", + EtwEventCounters.ullRegEnumerateValueKey); + + _Print("\t\tNtFlushKey: %I64u\n", + EtwEventCounters.ullRegFlush); + + _Print("\t\tNtOpenKey: %I64u\n", + EtwEventCounters.ullRegOpen); + + _Print("\t\tNtQueryKey: %I64u\n", + EtwEventCounters.ullRegQuery); + + _Print("\t\tNtQueryMultipleValueKey: %I64u\n", + EtwEventCounters.ullRegQueryMultipleValue); + + _Print("\t\tNtQueryValueKey: %I64u\n", + EtwEventCounters.ullRegQueryValue); + + _Print("\t\tNtSetInformationKey: %I64u\n", + EtwEventCounters.ullRegSetInformation); + + _Print("\t\tNtSetValueKey: %I64u\n", + EtwEventCounters.ullRegSetValue); + } + if (ETWMask.bThread) + { + _Print("\tThread\n"); + + _Print("\t\tStart: %I64u\n", EtwEventCounters.ullThreadStart); + _Print("\t\tEnd: %I64u\n", EtwEventCounters.ullThreadEnd); + } +} + +void ResultParser::_PrintDistribution(DistributionType dT, const vector& v, char* spc) +{ + if (dT == DistributionType::None) + { + return; + } + + switch (dT) + { + case DistributionType::Percent: + for (const auto &r : v) + { + _Print(spc); + _Print(" %3u%% of IO => [%2I64u%% - %3I64u%%) of target\n", + r._span, + r._dst.first, + r._dst.first + r._dst.second + ); + } + break; + + case DistributionType::Absolute: + { + const DistributionRange& last = *v.rbegin(); + UINT32 max = last._src + last._span; + + for (const auto &r : v) + { + _Print(spc); + // If this is a trimmed distribution (target was smaller than its range) + // then we need to rescale the trimmed IO% to 100%. Present this with a + // single decimal point, which may of course show rounding. + if (max < 100) + { + _Print(" %0.1f%% of IO => [", (double) 100 * r._span / max); + } + // Otherwise it is a simple 1-100% and can avoid rounding artifacts. + else + { + _Print(" %3u%% of IO => [", r._span); + } + + if (r._dst.first == 0) + { + // directly emit leading zero so we can align it + _Print(" 0 "); + } + else + { + _DisplayFileSize(r._dst.first, 6); + } + _Print(" - "); + // zero length occurs (only) in specification is a placeholder for end of target + if (r._dst.second) + { + _DisplayFileSize(r._dst.first + r._dst.second, 6); + _Print(")\n"); + } + else + { + _Print(" end)\n"); + } + } + } + break; + } +} + +class DistributionRef { +public: + + DistributionRef( + const string &TargetPath, + UINT32 Thread + ) + { + set s; + s.insert(Thread); + + _mTargetThreads.emplace(make_pair(TargetPath, std::move(s))); + } + + // + // Map a target to the set of threads referencing it with a given distribution + // + + map> _mTargetThreads; +}; + +namespace std +{ + template<> + struct less *> + { + // map by pointer, compare with the distributions + bool operator()(const vector * const &lhs, const vector * const &rhs) const + { + return *lhs < *rhs; + } + }; +} + +void ResultParser::_PrintEffectiveDistributions(const Results& results) +{ + // + // Effective distributions can be distinct per target if they vary in size. + // While not possible at the command line, more complex configurations can + // in general specify a distribution per target per thread. + // + // This deduplicates the effective distributions so that we report each + // with the target/thread list which used the (equivalent) distribution + // to access the target. + // + + bool header = false; + UINT32 threadNo = 0; + map *, DistributionRef> m; + + for (auto& thResult : results.vThreadResults) + { + for (auto& tgtResult : thResult.vTargetResults) + { + if (tgtResult.vDistributionRange.size()) + { + auto it = m.find(const_cast *>(&tgtResult.vDistributionRange)); + if (it == m.end()) + { + m.emplace(make_pair(const_cast *>(&tgtResult.vDistributionRange), + DistributionRef(tgtResult.sPath, threadNo))); + } + else + { + it->second._mTargetThreads[tgtResult.sPath].insert(threadNo); + } + } + } + + ++threadNo; + } + + for (auto& r : m) + { + if (!header) + { + header = true; + _Print("\nEffective IO Distributions\n--------------------------\n"); + } + // _Print("target: %s\n", r.second._sTargets.cbegin()->c_str()); + for (auto& tgt : r.second._mTargetThreads) + { + _Print("target: %s [thread:", tgt.first.c_str()); + + UINT32 lastTh = MAXUINT, runLen = 0; + + for (auto& th : tgt.second) + { + if (lastTh != MAXUINT) + { + // accumulate run? + if (lastTh + 1 == th) { + lastTh = th; + ++runLen; + continue; + } + + // end of run - indicate ellision of actual runs + if (runLen > 1) + { + _Print(" -"); + } + _Print(" %u", lastTh); + } + + // start new run (may be singular) + _Print(" %u", th); + lastTh = th; + runLen = 0; + } + + // terminate final run + if (runLen > 1) + { + _Print(" -"); + } + // don't show last thread twice if it terminated run + if (runLen) + { + _Print(" %u", lastTh); + } + + _Print("]\n"); + } + _PrintDistribution(DistributionType::Absolute, *r.first, ""); + } +} + +void ResultParser::_PrintTarget(const Target &target, bool fUseThreadsPerFile, bool fUseRequestsPerFile, bool fCompletionRoutines) +{ + if (target.GetPath().c_str()[0] == TEMPLATE_TARGET_PREFIX) + { + _Print("\tpath: template target '%s'\n", target.GetPath().c_str() + 1); + } + else + { + _Print("\tpath: '%s'\n", target.GetPath().c_str()); + } + _Print("\t\tthink time: %ums\n", target.GetThinkTime()); + _Print("\t\tburst size: %u\n", target.GetBurstSize()); + // TODO: completion routines/ports + + switch (target.GetCacheMode()) + { + case TargetCacheMode::Cached: + _Print("\t\tusing software cache\n"); + break; + case TargetCacheMode::DisableLocalCache: + _Print("\t\tlocal software cache disabled, remote cache enabled\n"); + break; + case TargetCacheMode::DisableOSCache: + _Print("\t\tsoftware cache disabled\n"); + break; + } + + if (target.GetWriteThroughMode() == WriteThroughMode::On) + { + // context-appropriate comment on writethrough + // if sw cache is disabled, commenting on sw write cache is possibly confusing + switch (target.GetCacheMode()) + { + case TargetCacheMode::Cached: + case TargetCacheMode::DisableLocalCache: + _Print("\t\thardware and software write caches disabled, writethrough on\n"); + break; + case TargetCacheMode::DisableOSCache: + _Print("\t\thardware write cache disabled, writethrough on\n"); + break; + } + } + else + { + _Print("\t\tusing hardware write cache, writethrough off\n"); + } + + if (target.GetMemoryMappedIoMode() == MemoryMappedIoMode::On) + { + _Print("\t\tmemory mapped I/O enabled"); + switch(target.GetMemoryMappedIoFlushMode()) + { + case MemoryMappedIoFlushMode::ViewOfFile: + _Print(", flush mode: FlushViewOfFile"); + break; + case MemoryMappedIoFlushMode::NonVolatileMemory: + _Print(", flush mode: FlushNonVolatileMemory"); + break; + case MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain: + _Print(", flush mode: FlushNonVolatileMemory with no drain"); + break; + } + _Print("\n"); + } + + if (target.GetZeroWriteBuffers()) + { + _Print("\t\tzeroing write buffers\n"); + } + + if (target.GetRandomDataWriteBufferSize() > 0) + { + _Print("\t\twrite buffer size: "); + _DisplayFileSize(target.GetRandomDataWriteBufferSize()); + _Print("\n"); + + string sWriteBufferSourcePath = target.GetRandomDataWriteBufferSourcePath(); + if (!sWriteBufferSourcePath.empty()) + { + _Print("\t\twrite buffer source: '%s'\n", sWriteBufferSourcePath.c_str()); + } + else + { + _Print("\t\twrite buffer source: random fill\n"); + } + } + + if (target.GetUseParallelAsyncIO()) + { + _Print("\t\tusing parallel async I/O\n"); + } + + if (target.GetWriteRatio() == 0) + { + _Print("\t\tperforming read test\n"); + } + else if (target.GetWriteRatio() == 100) + { + _Print("\t\tperforming write test\n"); + } + else + { + _Print("\t\tperforming mix test (read/write ratio: %d/%d)\n", 100 - target.GetWriteRatio(), target.GetWriteRatio()); + } + + _Print("\t\tblock size: "); + _DisplayFileSize(target.GetBlockSizeInBytes()); + _Print("\n"); + + if (target.GetRandomRatio() == 100) + { + _Print("\t\tusing random I/O (alignment: "); + } + else + { + if (target.GetRandomRatio() > 0) + { + _Print("\t\tusing mixed random/sequential I/O (%u%% random) (alignment/stride: ", target.GetRandomRatio()); + } + else + { + _Print("\t\tusing%s sequential I/O (stride: ", target.GetUseInterlockedSequential() ? " interlocked":""); + } + } + _DisplayFileSize(target.GetBlockAlignmentInBytes()); + _Print(")\n"); + + if (fUseRequestsPerFile) + { + _Print("\t\tnumber of outstanding I/O operations per thread: %d\n", target.GetRequestCount()); + } + else + { + _Print("\t\trelative IO weight in thread pool: %u\n", target.GetWeight()); + } + + if (0 != target.GetBaseFileOffsetInBytes()) + { + _Print("\t\tbase file offset: "); + _DisplayFileSize(target.GetBaseFileOffsetInBytes()); + _Print("\n"); + } + + if (0 != target.GetMaxFileSize()) + { + _Print("\t\tmax file size: "); + _DisplayFileSize(target.GetMaxFileSize()); + _Print("\n"); + } + + if (0 != target.GetThreadStrideInBytes()) + { + _Print("\t\tthread stride size: "); + _DisplayFileSize(target.GetThreadStrideInBytes()); + _Print("\n"); + } + + if (target.GetSequentialScanHint()) + { + _Print("\t\tusing FILE_FLAG_SEQUENTIAL_SCAN hint\n"); + } + + if (target.GetRandomAccessHint()) + { + _Print("\t\tusing FILE_FLAG_RANDOM_ACCESS hint\n"); + } + + if (target.GetTemporaryFileHint()) + { + _Print("\t\tusing FILE_ATTRIBUTE_TEMPORARY hint\n"); + } + + if (fUseThreadsPerFile) + { + _Print("\t\tthreads per file: %d\n", target.GetThreadsPerFile()); + } + if (target.GetRequestCount() > 1 && fUseThreadsPerFile) + { + if (fCompletionRoutines) + { + _Print("\t\tusing completion routines (ReadFileEx/WriteFileEx)\n"); + } + else + { + _Print("\t\tusing I/O Completion Ports\n"); + } + } + + if (target.GetIOPriorityHint() == IoPriorityHintVeryLow) + { + _Print("\t\tIO priority: very low\n"); + } + else if (target.GetIOPriorityHint() == IoPriorityHintLow) + { + _Print("\t\tIO priority: low\n"); + } + else if (target.GetIOPriorityHint() == IoPriorityHintNormal) + { + _Print("\t\tIO priority: normal\n"); + } + else + { + _Print("\t\tIO priority: unknown\n"); + } + + if (target.GetThroughputIOPS()) + { + _Print("\t\tthroughput rate-limited to %u IOPS\n", target.GetThroughputIOPS()); + } + else if (target.GetThroughputInBytesPerMillisecond()) + { + _Print("\t\tthroughput rate-limited to %u B/ms\n", target.GetThroughputInBytesPerMillisecond()); + } + + if (target.GetDistributionRange().size()) + { + _Print("\t\tIO Distribution:\n"); + _PrintDistribution(target.GetDistributionType(), target.GetDistributionRange(), "\t\t"); + } +} + +void ResultParser::_PrintTimeSpan(const TimeSpan& timeSpan) +{ + _Print("\tduration: %us\n", timeSpan.GetDuration()); + _Print("\twarm up time: %us\n", timeSpan.GetWarmup()); + _Print("\tcool down time: %us\n", timeSpan.GetCooldown()); + if (timeSpan.GetDisableAffinity()) + { + _Print("\taffinity disabled\n"); + } + if (timeSpan.GetMeasureLatency()) + { + _Print("\tmeasuring latency\n"); + } + if (timeSpan.GetCalculateIopsStdDev()) + { + _Print("\tgathering IOPS at intervals of %ums\n", timeSpan.GetIoBucketDurationInMilliseconds()); + } + _Print("\trandom seed: %u\n", timeSpan.GetRandSeed()); + if (timeSpan.GetThreadCount() != 0) + { + _Print("\tthread pool with %u threads\n", timeSpan.GetThreadCount()); + _Print("\tnumber of outstanding I/O operations per thread: %d\n", timeSpan.GetRequestCount()); + } + + const auto& vAffinity = timeSpan.GetAffinityAssignments(); + if ( vAffinity.size() > 0) + { + _Print("\tadvanced affinity round robin (group/core): "); + for (unsigned int x = 0; x < vAffinity.size(); ++x) + { + _Print("%u/%u", vAffinity[x].wGroup, vAffinity[x].bProc); + if (x < vAffinity.size() - 1) + { + _Print(", "); + } + } + _Print("\n"); + } + + if (timeSpan.GetRandomWriteData()) + { + _Print("\tgenerating random data for each write IO\n"); + _Print("\t WARNING: this increases the CPU cost of issuing writes and should only\n"); + _Print("\t be compared to other results using the -Zr flag\n"); + } + + vector vTargets(timeSpan.GetTargets()); + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + _PrintTarget(*i, (timeSpan.GetThreadCount() == 0), (timeSpan.GetThreadCount() == 0 || timeSpan.GetRequestCount() == 0), timeSpan.GetCompletionRoutines()); + } +} + +void ResultParser::_PrintProfile(const Profile& profile) +{ + _Print("\nCommand Line: %s\n", profile.GetCmdLine().c_str()); + _Print("\n"); + if (g_ExperimentFlags) + { + _Print("Experiment Flags: 0x%x (%u)\n", g_ExperimentFlags, g_ExperimentFlags); + _Print("\n"); + } + _Print("Input parameters:\n\n"); + if (profile.GetVerbose()) + { + _Print("\tusing verbose mode\n"); + } + + const vector& vTimeSpans = profile.GetTimeSpans(); + int c = 1; + for (auto i = vTimeSpans.begin(); i != vTimeSpans.end(); i++) + { + _Print("\ttimespan: %3d\n", c++); + _Print("\t-------------\n"); + _PrintTimeSpan(*i); + _Print("\n"); + } +} + +void ResultParser::_PrintSystemInfo(const SystemInformation& system) +{ + _Print(system.GetText().c_str()); +} + +void ResultParser::_PrintCpuUtilization(const Results& results, const SystemInformation& system) +{ + const auto& topo = system.processorTopology; + size_t procCount = results.vSystemProcessorPerfInfo.size(); + size_t baseProc = 0; + BYTE efficiencyClass = 0; + BYTE processorCore = 0; + + bool fMultiSocket = topo._vProcessorSocketInformation.size() > 1; + bool fMultiNode = topo._vProcessorNumaInformation.size() > 1; + bool fMultiGroup = topo._vProcessorGroupInformation.size() > 1; + + // + // Columns dynamically expand based on whether the system has multiple of the following, + // in hierarchical order, followed by CPU #: + // + // Socket NUMA Group Core Class + // + // Note that core & cpu number are group-relative, not absolute (or NUMA or socket relative) + // + + _Print("\n"); + if (fMultiSocket) { _Print("Socket | "); } + if (fMultiNode) { _Print("Node | "); } + if (fMultiGroup) { _Print("Group | "); } + if (topo._fSMT) { _Print("Core | "); } + if (topo._ubPerformanceEfficiencyClass) { _Print("Class | "); } + _Print("CPU | Usage | User | Kernel | Idle\n"); + if (fMultiSocket) { _Print("---------"); } + if (fMultiNode) { _Print("-------"); } + if (fMultiGroup) { _Print("--------"); } + if (topo._fSMT) { _Print("-------"); } + if (topo._ubPerformanceEfficiencyClass) { _Print("--------"); } + _Print("----------------------------------------\n"); + + double busyTime = 0; + double totalIdleTime = 0; + double totalUserTime = 0; + double totalKrnlTime = 0; + + for (const auto& group : topo._vProcessorGroupInformation) { + + // Sanity assert - results are sized to the sum of active processors + assert(baseProc + group._activeProcessorCount <= procCount); + + for (BYTE processor = 0; processor < group._activeProcessorCount; processor++) { + + long long fTime = results.vSystemProcessorPerfInfo[baseProc + processor].KernelTime.QuadPart + + results.vSystemProcessorPerfInfo[baseProc + processor].UserTime.QuadPart; + + double idleTime = 100.0 * results.vSystemProcessorPerfInfo[baseProc + processor].IdleTime.QuadPart / fTime; + double krnlTime = 100.0 * results.vSystemProcessorPerfInfo[baseProc + processor].KernelTime.QuadPart / fTime; + double userTime = 100.0 * results.vSystemProcessorPerfInfo[baseProc + processor].UserTime.QuadPart / fTime; + double usedTime = (krnlTime - idleTime) + userTime; + + if (fMultiSocket) { + _Print("%7u| ", topo.GetSocketOfProcessor(group._groupNumber, processor)); + } + if (fMultiNode) { + _Print("%5u| ", topo.GetNumaOfProcessor(group._groupNumber, processor)); + } + if (fMultiGroup) { + _Print("%6u| ", group._groupNumber); + } + processorCore = topo.GetCoreOfProcessor(group._groupNumber, processor, efficiencyClass); + if (topo._fSMT){ + _Print("%5u| ", processorCore); + } + if (topo._ubPerformanceEfficiencyClass) { + _Print("%5u%c| ", + efficiencyClass, + efficiencyClass == topo._ubPerformanceEfficiencyClass ? 'P' : ' '); + } + + _Print("%4u| %6.2lf%%| %6.2lf%%| %6.2lf%%| %6.2lf%%\n", + processor, + usedTime, + userTime, + krnlTime - idleTime, + idleTime); + + busyTime += usedTime; + totalIdleTime += idleTime; + totalUserTime += userTime; + totalKrnlTime += krnlTime; + } + + baseProc += group._activeProcessorCount; + } + + assert(baseProc == procCount); + + if (fMultiSocket) { _Print("---------"); } + if (fMultiNode) { _Print("-------"); } + if (fMultiGroup) { _Print("--------"); } + if (topo._fSMT) { _Print("-------"); } + if (topo._ubPerformanceEfficiencyClass) { _Print("--------"); } + _Print("----------------------------------------\n"); + + if (fMultiSocket) { _Print(" "); } + if (fMultiNode) { _Print(" "); } + if (fMultiGroup) { _Print(" "); } + if (topo._fSMT) { _Print(" "); } + if (topo._ubPerformanceEfficiencyClass) { _Print(" "); } + + _Print("avg.| %6.2lf%%| %6.2lf%%| %6.2lf%%| %6.2lf%%\n", + busyTime / procCount, + totalUserTime / procCount, + (totalKrnlTime - totalIdleTime) / procCount, + totalIdleTime / procCount); +} + +void ResultParser::_PrintSectionFieldNames(const TimeSpan& timeSpan) +{ + _Print("thread | bytes | I/Os | MiB/s | I/O per s %s%s%s| file\n", + timeSpan.GetMeasureLatency() ? "| AvgLat " : "", + timeSpan.GetCalculateIopsStdDev() ? "| IopsStdDev " : "", + timeSpan.GetMeasureLatency() ? "| LatStdDev " : ""); +} + +void ResultParser::_PrintSectionBorderLine(const TimeSpan& timeSpan) +{ + _Print("------------------------------------------------------------------%s%s%s------------\n", + timeSpan.GetMeasureLatency() ? "-----------" : "" , + timeSpan.GetCalculateIopsStdDev() ? "-------------" : "", + timeSpan.GetMeasureLatency() ? "------------" : ""); +} + +void ResultParser::_PrintSection(_SectionEnum section, const TimeSpan& timeSpan, const Results& results) +{ + double fTime = PerfTimer::PerfTimeToSeconds(results.ullTimeCount); + double fBucketTime = timeSpan.GetIoBucketDurationInMilliseconds() / 1000.0; + UINT64 ullTotalBytesCount = 0; + UINT64 ullTotalIOCount = 0; + Histogram totalLatencyHistogram; + IoBucketizer totalIoBucketizer; + + _PrintSectionFieldNames(timeSpan); + + _PrintSectionBorderLine(timeSpan); + + for (unsigned int iThread = 0; iThread < results.vThreadResults.size(); ++iThread) + { + const ThreadResults& threadResults = results.vThreadResults[iThread]; + for (unsigned int iFile = 0; iFile < threadResults.vTargetResults.size(); iFile++) + { + const TargetResults& targetResults = threadResults.vTargetResults[iFile]; + + UINT64 ullBytesCount = 0; + UINT64 ullIOCount = 0; + + Histogram latencyHistogram; + IoBucketizer ioBucketizer; + + if ((section == _SectionEnum::WRITE) || (section == _SectionEnum::TOTAL)) + { + ullBytesCount += targetResults.ullWriteBytesCount; + ullIOCount += targetResults.ullWriteIOCount; + + if (timeSpan.GetMeasureLatency()) + { + latencyHistogram.Merge(targetResults.writeLatencyHistogram); + totalLatencyHistogram.Merge(targetResults.writeLatencyHistogram); + } + + if (timeSpan.GetCalculateIopsStdDev()) + { + ioBucketizer.Merge(targetResults.writeBucketizer); + totalIoBucketizer.Merge(targetResults.writeBucketizer); + } + } + + if ((section == _SectionEnum::READ) || (section == _SectionEnum::TOTAL)) + { + ullBytesCount += targetResults.ullReadBytesCount; + ullIOCount += targetResults.ullReadIOCount; + + if (timeSpan.GetMeasureLatency()) + { + latencyHistogram.Merge(targetResults.readLatencyHistogram); + totalLatencyHistogram.Merge(targetResults.readLatencyHistogram); + } + + if (timeSpan.GetCalculateIopsStdDev()) + { + ioBucketizer.Merge(targetResults.readBucketizer); + totalIoBucketizer.Merge(targetResults.readBucketizer); + } + } + + _Print("%6u | %15llu | %12llu | %10.2f | %10.2f", + iThread, + ullBytesCount, + ullIOCount, + (double)ullBytesCount / 1024 / 1024 / fTime, + (double)ullIOCount / fTime); + + if (timeSpan.GetMeasureLatency()) + { + _Print(" | %8.3f", latencyHistogram.GetAvg() / 1000); + } + + if (timeSpan.GetCalculateIopsStdDev()) + { + double iopsStdDev = ioBucketizer.GetStandardDeviationIOPS() / fBucketTime; + _Print(" | %10.2f", iopsStdDev); + } + + if (timeSpan.GetMeasureLatency()) + { + if (latencyHistogram.GetSampleSize() > 0) + { + double latStdDev = latencyHistogram.GetStandardDeviation() / 1000; + _Print(" | %8.3f", latStdDev); + } + else + { + _Print(" | N/A"); + } + } + + _Print(" | %s (", targetResults.sPath.c_str()); + + _DisplayFileSize(targetResults.ullFileSize); + _Print(")\n"); + + ullTotalBytesCount += ullBytesCount; + ullTotalIOCount += ullIOCount; + } + } + + _PrintSectionBorderLine(timeSpan); + + _Print("total: %15llu | %12llu | %10.2f | %10.2f", + ullTotalBytesCount, + ullTotalIOCount, + (double)ullTotalBytesCount / 1024 / 1024 / fTime, + (double)ullTotalIOCount / fTime); + + if (timeSpan.GetMeasureLatency()) + { + _Print(" | %8.3f", totalLatencyHistogram.GetAvg()/1000); + } + + if (timeSpan.GetCalculateIopsStdDev()) + { + double iopsStdDev = totalIoBucketizer.GetStandardDeviationIOPS() / fBucketTime; + _Print(" | %10.2f", iopsStdDev); + } + + if (timeSpan.GetMeasureLatency()) + { + if (totalLatencyHistogram.GetSampleSize() > 0) + { + double latStdDev = totalLatencyHistogram.GetStandardDeviation() / 1000; + _Print(" | %8.3f", latStdDev); + } + else + { + _Print(" | N/A"); + } + } + + _Print("\n"); + + /// CrystalDiskMark + if (section == _SectionEnum::TOTAL) + { + _totalScore = (int)(ullTotalBytesCount / 1000 / fTime); + if (timeSpan.GetMeasureLatency()) + { + _averageLatency = totalLatencyHistogram.GetAvg() / 1000; + } + } +} + +void ResultParser::_PrintLatencyPercentiles(const Results& results) +{ + //Print one chart for each target IF more than one target + unordered_map> perTargetReadHistogram; + unordered_map> perTargetWriteHistogram; + unordered_map> perTargetTotalHistogram; + + for (const auto& thread : results.vThreadResults) + { + for (const auto& target : thread.vTargetResults) + { + std::string path = target.sPath; + + perTargetReadHistogram[path].Merge(target.readLatencyHistogram); + + perTargetWriteHistogram[path].Merge(target.writeLatencyHistogram); + + perTargetTotalHistogram[path].Merge(target.readLatencyHistogram); + perTargetTotalHistogram[path].Merge(target.writeLatencyHistogram); + } + } + + //Skip if only one target + if (perTargetTotalHistogram.size() > 1) { + for (auto i : perTargetTotalHistogram) + { + std::string path = i.first; + _Print("\nLatency distribution: %s\n", path.c_str()); + _PrintLatencyChart(perTargetReadHistogram[path], + perTargetWriteHistogram[path], + perTargetTotalHistogram[path]); + } + } + + //Print one chart for the latencies aggregated across all targets + Histogram readLatencyHistogram; + Histogram writeLatencyHistogram; + Histogram totalLatencyHistogram; + + for (const auto& thread : results.vThreadResults) + { + for (const auto& target : thread.vTargetResults) + { + readLatencyHistogram.Merge(target.readLatencyHistogram); + + writeLatencyHistogram.Merge(target.writeLatencyHistogram); + + totalLatencyHistogram.Merge(target.writeLatencyHistogram); + totalLatencyHistogram.Merge(target.readLatencyHistogram); + } + } + + _Print("\nTotal latency distribution:\n"); + _PrintLatencyChart(readLatencyHistogram, writeLatencyHistogram, totalLatencyHistogram); +} + +void ResultParser::_PrintLatencyChart(const Histogram& readLatencyHistogram, + const Histogram& writeLatencyHistogram, + const Histogram& totalLatencyHistogram) +{ + bool fHasReads = readLatencyHistogram.GetSampleSize() > 0; + bool fHasWrites = writeLatencyHistogram.GetSampleSize() > 0; + + _Print(" %%-ile | Read (ms) | Write (ms) | Total (ms)\n"); + _Print("----------------------------------------------\n"); + + string readMin = + fHasReads ? + Util::DoubleToStringHelper(readLatencyHistogram.GetMin()/1000) : + "N/A"; + + string writeMin = + fHasWrites ? + Util::DoubleToStringHelper(writeLatencyHistogram.GetMin() / 1000) : + "N/A"; + + _Print(" min | %10s | %10s | %10.3lf\n", + readMin.c_str(), writeMin.c_str(), totalLatencyHistogram.GetMin()/1000); + + PercentileDescriptor percentiles[] = + { + { 0.25, "25th" }, + { 0.50, "50th" }, + { 0.75, "75th" }, + { 0.90, "90th" }, + { 0.95, "95th" }, + { 0.99, "99th" }, + { 0.999, "3-nines" }, + { 0.9999, "4-nines" }, + { 0.99999, "5-nines" }, + { 0.999999, "6-nines" }, + { 0.9999999, "7-nines" }, + { 0.99999999, "8-nines" }, + { 0.999999999, "9-nines" }, + }; + + for (auto p : percentiles) + { + string readPercentile = + fHasReads ? + Util::DoubleToStringHelper(readLatencyHistogram.GetPercentile(p.Percentile) / 1000) : + "N/A"; + + string writePercentile = + fHasWrites ? + Util::DoubleToStringHelper(writeLatencyHistogram.GetPercentile(p.Percentile) / 1000) : + "N/A"; + + _Print("%7s | %10s | %10s | %10.3lf\n", + p.Name.c_str(), + readPercentile.c_str(), + writePercentile.c_str(), + totalLatencyHistogram.GetPercentile(p.Percentile)/1000); + } + + string readMax = Util::DoubleToStringHelper(readLatencyHistogram.GetMax() / 1000); + string writeMax = Util::DoubleToStringHelper(writeLatencyHistogram.GetMax() / 1000); + + _Print(" max | %10s | %10s | %10.3lf\n", + fHasReads ? readMax.c_str() : "N/A", + fHasWrites ? writeMax.c_str() : "N/A", + totalLatencyHistogram.GetMax()/1000); +} + +string ResultParser::ParseProfile(const Profile& profile) +{ + _sResult.clear(); + _PrintProfile(profile); + return _sResult; +} + +void ResultParser::_PrintWaitStats(const Results &results) +{ + _Print("Wait Statistics\n"); + _Print("thread | completion wait | throttle wait - sleep | lookaside | 0 - 7+ complete per lookaside\n"); + _Print("-----------------------------------------------------------------------------------------------\n"); + for (unsigned int iThread = 0; iThread < results.vThreadResults.size(); ++iThread) + { + const ThreadResults& threadResults = results.vThreadResults[iThread]; + _Print( + "%6u | %15llu | %13llu - %6llu | %9llu | %llu %llu %llu %llu %llu %llu %llu %llu\n", + iThread, + threadResults.WaitStats.Wait, + threadResults.WaitStats.ThrottleWait, + threadResults.WaitStats.ThrottleSleep, + threadResults.WaitStats.Lookaside, + threadResults.WaitStats.LookasideCompletion[0], + threadResults.WaitStats.LookasideCompletion[1], + threadResults.WaitStats.LookasideCompletion[2], + threadResults.WaitStats.LookasideCompletion[3], + threadResults.WaitStats.LookasideCompletion[4], + threadResults.WaitStats.LookasideCompletion[5], + threadResults.WaitStats.LookasideCompletion[6], + threadResults.WaitStats.LookasideCompletion[7]); + } +} + +string ResultParser::ParseResults(const Profile& profile, const SystemInformation& system, vector vResults) +{ + _sResult.clear(); + + _PrintProfile(profile); + _PrintSystemInfo(system); + + for (size_t iResult = 0; iResult < vResults.size(); iResult++) + { + _Print("\nResults for timespan %d:\n", iResult + 1); + _Print("*******************************************************************************\n"); + + const Results& results = vResults[iResult]; + const TimeSpan& timeSpan = profile.GetTimeSpans()[iResult]; + + double fTime = PerfTimer::PerfTimeToSeconds(results.ullTimeCount); //test duration + + char szFloatBuffer[1024]; + + // There either is a fixed number of threads for all files to share (GetThreadCount() > 0) or a number of threads per file. + // In the latter case vThreadResults.size() == number of threads per file * file count + size_t ulThreadCnt = (timeSpan.GetThreadCount() > 0) ? timeSpan.GetThreadCount() : results.vThreadResults.size(); + + if (fTime < 0.0000001) + { + _Print("The test was interrupted before the measurements began. No results are displayed.\n"); + } + else + { + // TODO: parameters.bCreateFile; + + _Print("\n"); + sprintf_s(szFloatBuffer, sizeof(szFloatBuffer), "actual test time:\t%.2lfs\n", fTime); + _Print("%s", szFloatBuffer); + _Print("thread count:\t\t%u\n", ulThreadCnt); + + if (timeSpan.GetThreadCount() != 0 && timeSpan.GetRequestCount() != 0) { + _Print("request count:\t\t%u\n", timeSpan.GetRequestCount()); + } + + _PrintCpuUtilization(results, system); + _PrintEffectiveDistributions(results); + + _Print("\nTotal IO\n"); + _PrintSection(_SectionEnum::TOTAL, timeSpan, results); + + _Print("\nRead IO\n"); + _PrintSection(_SectionEnum::READ, timeSpan, results); + + _Print("\nWrite IO\n"); + _PrintSection(_SectionEnum::WRITE, timeSpan, results); + + if (timeSpan.GetMeasureLatency()) + { + _PrintLatencyPercentiles(results); + } + + //etw + if (results.fUseETW) + { + _DisplayETW(results.EtwMask, results.EtwEventCounters); + _DisplayETWSessionInfo(results.EtwSessionInfo); + } + + // wait stats + if (profile.GetVerboseStats()) + { + _Print("\n"); + _PrintWaitStats(results); + } + } + } + + if (vResults.size() > 1) + { + _Print("\n\nTotals:\n"); + _Print("*******************************************************************************\n\n"); + _Print("type | bytes | I/Os | MiB/s | I/O per s\n"); + _Print("-------------------------------------------------------------------------------\n"); + + + UINT64 cbTotalWritten = 0; + UINT64 cbTotalRead = 0; + UINT64 cTotalWriteIO = 0; + UINT64 cTotalReadIO = 0; + UINT64 cTotalTicks = 0; + for (auto pResults = vResults.begin(); pResults != vResults.end(); pResults++) + { + double time = PerfTimer::PerfTimeToSeconds(pResults->ullTimeCount); + if (time >= 0.0000001) // skip timespans that were interrupted + { + cTotalTicks += pResults->ullTimeCount; + auto vThreadResults = pResults->vThreadResults; + for (auto pThreadResults = vThreadResults.begin(); pThreadResults != vThreadResults.end(); pThreadResults++) + { + for (auto pTargetResults = pThreadResults->vTargetResults.begin(); pTargetResults != pThreadResults->vTargetResults.end(); pTargetResults++) + { + cbTotalRead += pTargetResults->ullReadBytesCount; + cbTotalWritten += pTargetResults->ullWriteBytesCount; + cTotalReadIO += pTargetResults->ullReadIOCount; + cTotalWriteIO += pTargetResults->ullWriteIOCount; + } + } + } + } + + double totalTime = PerfTimer::PerfTimeToSeconds(cTotalTicks); + + _Print("write | %15I64u | %12I64u | %10.2lf | %10.2lf\n", + cbTotalWritten, + cTotalWriteIO, + (double)cbTotalWritten / 1024 / 1024 / totalTime, + (double)cTotalWriteIO / totalTime); + + _Print("read | %15I64u | %12I64u | %10.2lf | %10.2lf\n", + cbTotalRead, + cTotalReadIO, + (double)cbTotalRead / 1024 / 1024 / totalTime, + (double)cTotalReadIO / totalTime); + _Print("-------------------------------------------------------------------------------\n"); + _Print("total | %15I64u | %12I64u | %10.2lf | %10.2lf\n\n", + cbTotalRead + cbTotalWritten, + cTotalReadIO + cTotalWriteIO, + (double)(cbTotalRead + cbTotalWritten) / 1024 / 1024 / totalTime, + (double)(cTotalReadIO + cTotalWriteIO) / totalTime); + + _Print("total test time:\t%.2lfs\n", totalTime); + } + + return _sResult; +} diff --git a/CristalDiskMark/source/diskspd22/SECURITY.md b/CristalDiskMark/source/diskspd22/SECURITY.md new file mode 100644 index 0000000..869fdfe --- /dev/null +++ b/CristalDiskMark/source/diskspd22/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). + + diff --git a/CristalDiskMark/source/diskspd22/UnitTests/CmdLineParser/CmdLineParser.UnitTests.cpp b/CristalDiskMark/source/diskspd22/UnitTests/CmdLineParser/CmdLineParser.UnitTests.cpp new file mode 100644 index 0000000..036e18c --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/CmdLineParser/CmdLineParser.UnitTests.cpp @@ -0,0 +1,4771 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "StdAfx.h" +#include "CmdLineParser.h" +#include "CmdLineParser.UnitTests.h" +#include + +using namespace WEX::TestExecution; +using namespace WEX::Logging; + +namespace UnitTests +{ + bool ModuleSetup() + { + return true; + } + + bool ModuleCleanup() + { + return true; + } + + bool CmdLineParserUnitTests::ClassSetup() + { + return true; + } + + bool CmdLineParserUnitTests::ClassCleanup() + { + return true; + } + + bool CmdLineParserUnitTests::MethodSetup() + { + return true; + } + + bool CmdLineParserUnitTests::MethodCleanup() + { + return true; + } + + void CmdLineParserUnitTests::Test_GetSizeInBytes() + { + CmdLineParser p; + UINT64 ullResult = 0; + VERIFY_IS_TRUE(p._GetSizeInBytes("0", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == 0); + + VERIFY_IS_TRUE(p._GetSizeInBytes("1", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == 1); + + VERIFY_IS_TRUE(p._GetSizeInBytes("2", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == 2); + + VERIFY_IS_TRUE(p._GetSizeInBytes("10", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == 10); + + VERIFY_IS_TRUE(p._GetSizeInBytes("4096", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == 4096); + + VERIFY_IS_TRUE(p._GetSizeInBytes("123908798324", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == 123908798324); + + // _GetSizeInBytes shouldn't modify ullResult on if the input string is incorrect + ullResult = 9; + VERIFY_IS_TRUE(p._GetSizeInBytes("10a", ullResult, nullptr) == false); + VERIFY_IS_TRUE(ullResult == 9); + + // block + VERIFY_IS_TRUE(p._GetSizeInBytes("1b", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == p._dwBlockSize); + + VERIFY_IS_TRUE(p._GetSizeInBytes("3B", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == 3 * p._dwBlockSize); + + VERIFY_IS_TRUE(p._GetSizeInBytes("30b", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == 30 * p._dwBlockSize); + + VERIFY_IS_TRUE(p._GetSizeInBytes("30 b", ullResult, nullptr) == false); + VERIFY_IS_TRUE(p._GetSizeInBytes("30 B", ullResult, nullptr) == false); + + // KB + VERIFY_IS_TRUE(p._GetSizeInBytes("1K", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == 1024); + + VERIFY_IS_TRUE(p._GetSizeInBytes("3K", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == 3 * 1024); + + VERIFY_IS_TRUE(p._GetSizeInBytes("30K", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == 30 * 1024); + + VERIFY_IS_TRUE(p._GetSizeInBytes("30 K", ullResult, nullptr) == false); + VERIFY_IS_TRUE(p._GetSizeInBytes("30KB", ullResult, nullptr) == false); + + // MB + VERIFY_IS_TRUE(p._GetSizeInBytes("1M", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == 1024 * 1024); + + VERIFY_IS_TRUE(p._GetSizeInBytes("4M", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == 4 * 1024 * 1024); + + VERIFY_IS_TRUE(p._GetSizeInBytes("50M", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == 50 * 1024 * 1024); + + VERIFY_IS_TRUE(p._GetSizeInBytes("40 M", ullResult, nullptr) == false); + VERIFY_IS_TRUE(p._GetSizeInBytes("40MB", ullResult, nullptr) == false); + + // GB + VERIFY_IS_TRUE(p._GetSizeInBytes("1G", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == 1024 * 1024 * 1024); + + VERIFY_IS_TRUE(p._GetSizeInBytes("6G", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == (UINT64)6 * 1024 * 1024 * 1024); + + VERIFY_IS_TRUE(p._GetSizeInBytes("70G", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == (UINT64)70 * 1024 * 1024 * 1024); + + VERIFY_IS_TRUE(p._GetSizeInBytes("70 G", ullResult, nullptr) == false); + VERIFY_IS_TRUE(p._GetSizeInBytes("70GB", ullResult, nullptr) == false); + + // TB + VERIFY_IS_TRUE(p._GetSizeInBytes("1T", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == (UINT64)1024 * 1024 * 1024 * 1024); + + VERIFY_IS_TRUE(p._GetSizeInBytes("6T", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == (UINT64)6 * 1024 * 1024 * 1024 * 1024); + + VERIFY_IS_TRUE(p._GetSizeInBytes("70T", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == (UINT64)70 * 1024 * 1024 * 1024 * 1024); + + VERIFY_IS_TRUE(p._GetSizeInBytes("70 T", ullResult, nullptr) == false); + VERIFY_IS_TRUE(p._GetSizeInBytes("70TB", ullResult, nullptr) == false); + // check overflows + // MAXUINT64 == 18446744073709551615 + ullResult = 0; + VERIFY_IS_TRUE(p._GetSizeInBytes("18446744073709551615", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == MAXUINT64); + + // MAXUINT64 + 1 + VERIFY_IS_TRUE(p._GetSizeInBytes("18446744073709551616", ullResult, nullptr) == false); + + // MAXUINT64 / 1024 = 18014398509481983 + ullResult = 0; + VERIFY_IS_TRUE(p._GetSizeInBytes("18014398509481983K", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == (MAXUINT64 >> 10) << 10); + VERIFY_IS_TRUE(p._GetSizeInBytes("18014398509481984K", ullResult, nullptr) == false); + + // MAXUINT64 / 1024^2 = 17592186044415 + ullResult = 0; + VERIFY_IS_TRUE(p._GetSizeInBytes("17592186044415M", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == (MAXUINT64 >> 20) << 20); + VERIFY_IS_TRUE(p._GetSizeInBytes("17592186044416M", ullResult, nullptr) == false); + + // MAXUINT64 / 1024^3 = 17179869183 + ullResult = 0; + VERIFY_IS_TRUE(p._GetSizeInBytes("17179869183G", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == (MAXUINT64 >> 30) << 30); + VERIFY_IS_TRUE(p._GetSizeInBytes("17179869184G", ullResult, nullptr) == false); + + // block + p._dwBlockSize = 1024; + ullResult = 0; + VERIFY_IS_TRUE(p._GetSizeInBytes("18014398509481983b", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == (MAXUINT64 >> 10) << 10); + VERIFY_IS_TRUE(p._GetSizeInBytes("18014398509481984b", ullResult, nullptr) == false); + } + + void CmdLineParserUnitTests::TestParseCmdLineAssignAffinity() + { + CmdLineParser p; + struct Synchronization s = {}; + + // base case + { + Profile profile; + const char *argv[] = { "foo", "-a", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_IS_TRUE(vAffinity.empty()); + } + + // base case + { + Profile profile; + const char *argv[] = { "foo", "-ag", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_IS_TRUE(vAffinity.empty()); + } + + // no group spec case + { + Profile profile; + const char *argv[] = { "foo", "-a1,4,6", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)3); + VERIFY_ARE_EQUAL(vAffinity[0].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[0].bProc, (BYTE)1); + VERIFY_ARE_EQUAL(vAffinity[1].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[1].bProc, (BYTE)4); + VERIFY_ARE_EQUAL(vAffinity[2].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[2].bProc, (BYTE)6); + } + + // single group spec + { + Profile profile; + const char *argv[] = { "foo", "-ag0,1,4,6", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)3); + VERIFY_ARE_EQUAL(vAffinity[0].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[0].bProc, (BYTE)1); + VERIFY_ARE_EQUAL(vAffinity[1].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[1].bProc, (BYTE)4); + VERIFY_ARE_EQUAL(vAffinity[2].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[2].bProc, (BYTE)6); + } + + // multiple group spec + { + Profile profile; + const char *argv[] = { "foo", "-ag0,1,4,6,g1,2,5,7", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)6); + VERIFY_ARE_EQUAL(vAffinity[0].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[0].bProc, (BYTE)1); + VERIFY_ARE_EQUAL(vAffinity[1].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[1].bProc, (BYTE)4); + VERIFY_ARE_EQUAL(vAffinity[2].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[2].bProc, (BYTE)6); + VERIFY_ARE_EQUAL(vAffinity[3].wGroup, 1); + VERIFY_ARE_EQUAL(vAffinity[3].bProc, (BYTE)2); + VERIFY_ARE_EQUAL(vAffinity[4].wGroup, 1); + VERIFY_ARE_EQUAL(vAffinity[4].bProc, (BYTE)5); + VERIFY_ARE_EQUAL(vAffinity[5].wGroup, 1); + VERIFY_ARE_EQUAL(vAffinity[5].bProc, (BYTE)7); + } + + // multiple group spec across two instances of -ag + { + Profile profile; + const char *argv[] = { "foo", "-ag0,1,4,6", "-ag1,2,5,7", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)6); + VERIFY_ARE_EQUAL(vAffinity[0].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[0].bProc, (BYTE)1); + VERIFY_ARE_EQUAL(vAffinity[1].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[1].bProc, (BYTE)4); + VERIFY_ARE_EQUAL(vAffinity[2].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[2].bProc, (BYTE)6); + VERIFY_ARE_EQUAL(vAffinity[3].wGroup, 1); + VERIFY_ARE_EQUAL(vAffinity[3].bProc, (BYTE)2); + VERIFY_ARE_EQUAL(vAffinity[4].wGroup, 1); + VERIFY_ARE_EQUAL(vAffinity[4].bProc, (BYTE)5); + VERIFY_ARE_EQUAL(vAffinity[5].wGroup, 1); + VERIFY_ARE_EQUAL(vAffinity[5].bProc, (BYTE)7); + } + + // now various syntax error cases + // just group spec + { + Profile profile; + const char *argv[] = { "foo", "-ag0", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // multiple g + { + Profile profile; + const char *argv[] = { "foo", "-agg", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // group with no number + { + Profile profile; + const char *argv[] = { "foo", "-ag,0,1,2", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // trailing , + { + Profile profile; + const char *argv[] = { "foo", "-a1,", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // trailing , in group spec form + { + Profile profile; + const char *argv[] = { "foo", "-ag1,0,", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // trailing g in group spec form + { + Profile profile; + const char *argv[] = { "foo", "-ag1,0,g", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // trailing group spec form + { + Profile profile; + const char *argv[] = { "foo", "-ag1,0,g2", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // junk chars + { + Profile profile; + const char *argv[] = { "foo", "-ax", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // out-of-range processor index (BYTE) + { + Profile profile; + const char *argv[] = { "foo", "-ag0,300", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // out-of-range group index (WORD) + { + Profile profile; + const char *argv[] = { "foo", "-ag70000,1", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + } + + void CmdLineParserUnitTests::TestParseCmdLine() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b4K", "-w84", "-a1,4,6", "testfile.dat", "testfile2.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b4K -w84 -a1,4,6 testfile.dat testfile2.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)3); + VERIFY_ARE_EQUAL(vAffinity[0].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[0].bProc, (BYTE)1); + VERIFY_ARE_EQUAL(vAffinity[1].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[1].bProc, (BYTE)4); + VERIFY_ARE_EQUAL(vAffinity[2].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[2].bProc, (BYTE)6); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)2); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(4 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + + t = vTargets[1]; + VERIFY_IS_TRUE(t.GetPath().compare("testfile2.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(4 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineBlockSize() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-a1,4,6", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -a1,4,6 testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)3); + VERIFY_ARE_EQUAL(vAffinity[0].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[0].bProc, (BYTE)1); + VERIFY_ARE_EQUAL(vAffinity[1].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[1].bProc, (BYTE)4); + VERIFY_ARE_EQUAL(vAffinity[2].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[2].bProc, (BYTE)6); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineGroupAffinity() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-ag", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -ag testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + //TimeSpan span = vSpans[0]; + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineBaseMaxTarget() + { + CmdLineParser p; + struct Synchronization s = {}; + + { + Profile profile; + const char *argv[] = { "foo", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo testfile.dat") == 0); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + + // defaults = 0 + const auto& t(vTargets[0]); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), (UINT64) 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), (UINT64) 0); + } + + { + // base 5MiB + Profile profile; + const char *argv[] = { "foo", "-B5m", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -B5m testfile.dat") == 0); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + + const auto& t(vTargets[0]); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), (UINT64)(5 * 1024 * 1024)); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), (UINT64) 0); + } + + { + // base 5MiB, length 1MiB -> 6MiB + Profile profile; + const char *argv[] = { "foo", "-B5m:1m", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -B5m:1m testfile.dat") == 0); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + + const auto& t(vTargets[0]); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), (UINT64)(5 * 1024 * 1024)); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), (UINT64)(6 * 1024 * 1024)); + } + + { + // base 5MiB, max 6MiB + Profile profile; + const char *argv[] = { "foo", "-B5m", "-f6m", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -B5m -f6m testfile.dat") == 0); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + + const auto& t(vTargets[0]); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), (UINT64)(5 * 1024 * 1024)); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), (UINT64)(6 * 1024 * 1024)); + } + + { + // cannot specify -f/-Bb:l together + Profile profile; + const char *argv[] = { "foo", "-B5m:1m", "-f6m", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + + { + // cannot specify -B twice (2x b:l) + Profile profile; + const char *argv[] = { "foo", "-B5m:1m", "-B5m:1m", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + + { + // cannot specify -B twice (b:l and b) + Profile profile; + const char *argv[] = { "foo", "-B5m:1m", "-B5m", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + + { + // cannot specify -B twice (b and b) + Profile profile; + const char *argv[] = { "foo", "-B5m", "-B5m", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + + { + // cannot specify -f twice (f and f) + Profile profile; + const char *argv[] = { "foo", "-f5m", "-f6m", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + + { + // cannot specify max twice (b:l and f) + Profile profile; + const char *argv[] = { "foo", "-B5m:1m", "-f6m", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + + { + // junk after -Bbase + Profile profile; + const char *argv[] = { "foo", "-B5mx", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + + { + // sep but no length + Profile profile; + const char *argv[] = { "foo", "-B5m:", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + + { + // sep but junk length + Profile profile; + const char *argv[] = { "foo", "-B5m:j", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + + { + // sep but bad length spec + Profile profile; + const char *argv[] = { "foo", "-B5m:1x", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + + { + // sep but extra after length spec + Profile profile; + const char *argv[] = { "foo", "-B5m:1mx", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + } + + void CmdLineParserUnitTests::TestParseCmdLineHintFlag() + { + CmdLineParser p; + struct Synchronization s = {}; + + { + Profile profile; + const char *argv[] = { "foo", "-b128K", "-w84", "-fs", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -fs testfile.dat") == 0); + VerifyParseCmdLineAccessHints(profile, false, true, false); + } + + { + Profile profile; + const char *argv[] = { "foo", "-b128K", "-w84", "-fr", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -fr testfile.dat") == 0); + VerifyParseCmdLineAccessHints(profile, true, false, false); + } + + { + Profile profile; + const char *argv[] = { "foo", "-b128K", "-w84", "-ft", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -ft testfile.dat") == 0); + VerifyParseCmdLineAccessHints(profile, false, false, true); + } + + { + Profile profile; + const char *argv[] = { "foo", "-b128K", "-w84", "-frst", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -frst testfile.dat") == 0); + VerifyParseCmdLineAccessHints(profile, true, true, true); + } + } + + void CmdLineParserUnitTests::VerifyParseCmdLineAccessHints(Profile &profile, bool RandomAccess, bool SequentialScan, bool TemporaryFile) + { + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == SequentialScan); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == RandomAccess); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == TemporaryFile); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineDisableAllCacheMode1() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-h", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -h testfile.dat") == 0); + + VerifyParseCmdLineDisableAllCache(profile); + } + + void CmdLineParserUnitTests::TestParseCmdLineDisableAllCacheMode2() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-Sh", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -Sh testfile.dat") == 0); + + VerifyParseCmdLineDisableAllCache(profile); + } + + void CmdLineParserUnitTests::VerifyParseCmdLineDisableAllCache(Profile &profile) + { + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::DisableOSCache); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::On); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineConflictingCacheModes() + { + CmdLineParser p; + struct Synchronization s = {}; + + // conflict bu in either order + { + Profile profile; + const char *argv[] = { "foo", "-Sub", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + { + Profile profile; + const char *argv[] = { "foo", "-Sbu", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // conflict ru + { + Profile profile; + const char *argv[] = { "foo", "-Sru", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // conflict mu + { + Profile profile; + const char *argv[] = { "foo", "-Smu", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // conflict with -h/-Sb, either order + { + Profile profile; + const char *argv[] = { "foo", "-Sb", "-h", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + { + Profile profile; + const char *argv[] = { "foo", "-h", "-Sb", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // conflict with -h/-Sm + { + Profile profile; + const char *argv[] = { "foo", "-Sm", "-h", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // multiple with -h/-Suw, either order + { + Profile profile; + const char *argv[] = { "foo", "-Suw", "-h", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + { + Profile profile; + const char *argv[] = { "foo", "-h", "-Suw", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // multiple with -S/-Su + { + Profile profile; + const char *argv[] = { "foo", "-S", "-Su", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // multiple with -Sb/-Sb (same) + { + Profile profile; + const char *argv[] = { "foo", "-Sb", "-Sb", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // multiple with -Sm/-Sm + { + Profile profile; + const char *argv[] = { "foo", "-Sm", "-Sm", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // invalid option + { + Profile profile; + const char *argv[] = { "foo", "-Sx", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + } + + void CmdLineParserUnitTests::TestParseCmdLineDisableAffinity() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-n", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -n testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == true); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineDisableAffinityConflict() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-n", "-a1,2", "testfile.dat" }; + + // -n cannot be used with -a + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + + void CmdLineParserUnitTests::TestParseCmdLineVerbose() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-v", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == true); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -v testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineDisableOSCache() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-S", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -S testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::DisableOSCache); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineDisableLocalCache() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-Sr", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -Sr testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::DisableLocalCache); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineBufferedWriteThrough() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-Sbw", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -Sbw testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::On); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::VerifyParseCmdLineMappedIO(Profile &profile, MemoryMappedIoFlushMode FlushMode) + { + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::On); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == FlushMode); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineMappedIO() + { + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-Sm", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -Sm testfile.dat") == 0); + VerifyParseCmdLineMappedIO(profile, MemoryMappedIoFlushMode::Undefined); + } + + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-Sm", "-Nv", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -Sm -Nv testfile.dat") == 0); + VerifyParseCmdLineMappedIO(profile, MemoryMappedIoFlushMode::ViewOfFile); + } + + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-Sm", "-Nn", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -Sm -Nn testfile.dat") == 0); + VerifyParseCmdLineMappedIO(profile, MemoryMappedIoFlushMode::NonVolatileMemory); + } + + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-Sm", "-Ni", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -Sm -Ni testfile.dat") == 0); + VerifyParseCmdLineMappedIO(profile, MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain); + } + } + + void CmdLineParserUnitTests::TestParseCmdLineUseCompletionRoutines() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-x", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -x testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == true); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineRandSeed() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-z1234", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -z1234 testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)1234); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineRandSeedGetTickCount() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-z", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -z testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_IS_TRUE(vSpans[0].GetRandSeed() != 0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineWarmupAndCooldown() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-C12", "-W345", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -C12 -W345 testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)345); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)12); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineDurationAndProgress() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-d12", "-P345", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 345); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -d12 -P345 testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)12); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineUseParallelAsyncIO() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-p", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -p testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == true); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineUseLargePages() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-l", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -l testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == true); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineOverlappedCountAndBaseOffset() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-o123", "-B512k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -o123 -B512k testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)123); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 512 * 1024); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineCreateFileAndMaxFileSize() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-c23", "-f4M", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -c23 -f4M testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == true); + VERIFY_ARE_EQUAL(t.GetFileSize(), 23); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), (4 * 1024 * 1024)); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineBurstSizeAndThinkTime() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-i23", "-j567", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -i23 -j567 testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == true); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)23); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)567); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == true); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineTotalThreadCountAndThroughput() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-F23", "-g567", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -F23 -g567 testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)23); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)567); + } + + void CmdLineParserUnitTests::TestParseCmdLineRandomIOAlignment() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-r23M", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -r23M testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 100); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), (23 * 1024 * 1024)); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineStrideSize() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-s567K", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -s567K testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), (567 * 1024)); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineThreadsPerFileAndThreadStride() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-t23", "-T512K", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -t23 -T512K testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)23); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), (512 * 1024)); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineEtwUsePagedMemory() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-ep", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -ep testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == true); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == true); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineEtwPROCESS() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-ePROCESS", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -ePROCESS testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == true); + VERIFY_IS_TRUE(profile.GetEtwProcess() == true); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineEtwTHREAD() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-eTHREAD", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -eTHREAD testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == true); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == true); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineEtwIMAGE_LOAD() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-eIMAGE_LOAD", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -eIMAGE_LOAD testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == true); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == true); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineEtwDISK_IO() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-eDISK_IO", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -eDISK_IO testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == true); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == true); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineEtwNETWORK() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-eNETWORK", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -eNETWORK testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == true); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == true); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineEtwREGISTRY() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-eREGISTRY", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -eREGISTRY testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == true); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == true); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineEtwMEMORY_PAGE_FAULTS() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-eMEMORY_PAGE_FAULTS", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -eMEMORY_PAGE_FAULTS testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == true); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == true); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineEtwMEMORY_HARD_FAULTS() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-eMEMORY_HARD_FAULTS", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -eMEMORY_HARD_FAULTS testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == true); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == true); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineEtwUsePerfTimer() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-eq", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -eq testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == true); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == true); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineEtwUseSystemTimer() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-es", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -es testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == true); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == true); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineEtwUseCycleCount() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-ec", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -ec testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == true); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == true); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineIOPriority() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-I2", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -I2 testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_IS_TRUE(t.GetIOPriorityHint() == IoPriorityHintLow); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineMeasureLatency() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-L", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -L testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == true); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineZeroWriteBuffers() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-Z", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -Z testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == true); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSize(), 0); + VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSourcePath(), ""); + } + + void CmdLineParserUnitTests::TestParseCmdLineRandomWriteBuffers() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-Zr", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -Zr testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == true); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSize(), 0); + VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSourcePath(), ""); + } + + void CmdLineParserUnitTests::TestGetRandomDataWriteBufferData() + { + CmdLineParser p; + UINT64 cb; + string sPath; + + p._GetRandomDataWriteBufferData("11332", cb, sPath); + VERIFY_ARE_EQUAL(cb, 11332); + VERIFY_IS_TRUE(sPath == ""); + + cb = 0; + sPath = ""; + p._GetRandomDataWriteBufferData("11332,", cb, sPath); + VERIFY_ARE_EQUAL(cb, 11332); + VERIFY_IS_TRUE(sPath == ""); + + cb = 0; + sPath = ""; + p._GetRandomDataWriteBufferData("11332,x:\\foo\\bar", cb, sPath); + VERIFY_ARE_EQUAL(cb, 11332); + VERIFY_IS_TRUE(sPath == "x:\\foo\\bar"); + + cb = 0; + sPath = ""; + p._GetRandomDataWriteBufferData("2M", cb, sPath); + VERIFY_ARE_EQUAL(cb, 2 * 1024 * 1024); + VERIFY_IS_TRUE(sPath == ""); + + cb = 0; + sPath = ""; + p._GetRandomDataWriteBufferData("2M,x:\\foo\\bar", cb, sPath); + VERIFY_ARE_EQUAL(cb, 2 * 1024 * 1024); + VERIFY_IS_TRUE(sPath == "x:\\foo\\bar"); + } + + void CmdLineParserUnitTests::TestParseCmdLineWriteBufferContentRandomNoFilePath() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-Z2M", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + + vector vSpans(profile.GetTimeSpans()); + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_FALSE(t.GetZeroWriteBuffers()); + VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSize(), (size_t)(2 * 1024 * 1024)); + VERIFY_IS_TRUE(t.GetRandomDataWriteBufferSourcePath() == ""); + } + + void CmdLineParserUnitTests::TestParseCmdLineWriteBufferContentRandomWithFilePath() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-Z3M,x:\\foo\\bar.baz", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + + vector vSpans(profile.GetTimeSpans()); + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_FALSE(t.GetZeroWriteBuffers()); + VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSize(), (size_t)(3 * 1024 * 1024)); + VERIFY_IS_TRUE(t.GetRandomDataWriteBufferSourcePath() == "x:\\foo\\bar.baz"); + } + + void CmdLineParserUnitTests::TestParseCmdLineInterlockedSequential() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-si", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -si testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == true); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineInterlockedSequentialWithStride() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-si567K", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -si567K testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == true); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), (567 * 1024)); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineTotalThreadCountAndTotalRequestCount() + { + CmdLineParser p; + Profile profile; + struct Synchronization s = {}; + const char *argv[] = { "foo", "-b128K", "-w84", "-F23", "-O8", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetVerbose() == false); + VERIFY_IS_TRUE(profile.GetProgress() == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -F23 -O8 testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)5); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)0); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)23); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)8); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == false); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == false); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == false); + VERIFY_IS_TRUE(vSpans[0].GetRandomWriteData() == false); + + const auto& vAffinity(vSpans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)0); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); + } + + void CmdLineParserUnitTests::TestParseCmdLineThroughput() + { + CmdLineParser p; + struct Synchronization s = {}; + + { + Profile profile; + const char *argv[] = { "foo", "-b128K", "-g10i", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetThroughputInBytesPerMillisecond(), (DWORD)((128*1024*10)/1000)); + VERIFY_IS_TRUE(profile.GetTimeSpans()[0].GetTargets()[0].GetThroughputIOPS() == 10); + } + { + Profile profile; + const char *argv[] = { "foo", "-b128K", "-g1024", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetTimeSpans()[0].GetTargets()[0].GetThroughputInBytesPerMillisecond() == 1024); + VERIFY_IS_TRUE(profile.GetTimeSpans()[0].GetTargets()[0].GetThroughputIOPS() == 0); + } + + // Invalid cases: valid unit on wrong side, no digits, zeroes, bad unit + + { + Profile profile; + const char *argv[] = { "foo", "-b128K", "-gi100", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-b128K", "-g0", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-b128K", "-g0i", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-b128K", "-g", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-b128K", "-gi", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-b128K", "-g100x", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + } + + void CmdLineParserUnitTests::TestParseCmdLineRandomSequentialMixed() + { + // Coverage for -rs and combinations of conflicts with -r/-s/-rs + + CmdLineParser p; + struct Synchronization s = {}; + + // + // -rs cases + // + + // Isolated + + { + Profile profile; + const char *argv[] = { "foo", "-rs50", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetRandomRatio(), (UINT32) 50); + } + + // Combined with -r, in any order + + { + Profile profile; + const char *argv[] = { "foo", "-rs50", "-r8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetRandomRatio(), (UINT32) 50); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetBlockAlignmentInBytes(), 8*1024); + } + { + Profile profile; + const char *argv[] = { "foo", "-r8k", "-rs50", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetRandomRatio(), (UINT32) 50); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetBlockAlignmentInBytes(), 8*1024); + } + + // Combined with -r, in any order (don't care block size) + // While the -r in this order has no effect and could be flagged as an error, we don't currently parse to this level + + { + Profile profile; + const char *argv[] = { "foo", "-rs50", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetRandomRatio(), (UINT32) 50); + } + { + Profile profile; + const char *argv[] = { "foo", "-r", "-rs50", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetRandomRatio(), (UINT32) 50); + } + + // Now for conflict cases + + // Combined with -s in any order + + { + Profile profile; + const char *argv[] = { "foo", "-rs50", "-s8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s8k", "-rs50", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + + // Combined with -s in any order + + { + Profile profile; + const char *argv[] = { "foo", "-rs50", "-s", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s", "-rs50", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + + // -r/-s conflict + + { + Profile profile; + const char *argv[] = { "foo", "-r", "-s", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + + // 3-way conflict + // If it were more important, we could/should enumerate the orderings + { + Profile profile; + const char *argv[] = { "foo", "-rs50", "-s", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s", "-rs50", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s", "-r", "-rs50", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + + // 3-way with -s + { + Profile profile; + const char *argv[] = { "foo", "-rs50", "-s8k", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s8k", "-rs50", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s8k", "-r", "-rs50", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + + // 3-way with -r + { + Profile profile; + const char *argv[] = { "foo", "-rs50", "-s", "-r8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s", "-rs50", "-r8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s", "-r8k", "-rs50", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + + // Duplicated specs + { + Profile profile; + const char *argv[] = { "foo", "-s", "-s8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s8k", "-s", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s", "-s", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s8k", "-s8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-r", "-r8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-r8k", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-r", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-r8k", "-r8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-r8k", "-r4k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + + // Bounds checks: -rs100 is OK w/wo random alignment and -rs0 is rejected + { + Profile profile; + const char *argv[] = { "foo", "-rs100", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + { + Profile profile; + const char *argv[] = { "foo", "-rs100", "-r8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + { + Profile profile; + const char *argv[] = { "foo", "-r", "-rs100", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + { + Profile profile; + const char *argv[] = { "foo", "-r8k", "-rs100", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + { + Profile profile; + const char *argv[] = { "foo", "-rs0", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-rs0", "-r8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-rs0", "-s", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-rs0", "-s8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + } + + void CmdLineParserUnitTests::TestParseCmdLineTargetDistribution() + { + // coverage for parsing of cmdline target distributions (percent/absolute) + + CmdLineParser p; + struct Synchronization s = {}; + + // + // Positive cases - these match the ResultParser UTs + // + + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10/10:10/10:0/10", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + + auto t = profile.GetTimeSpans()[0].GetTargets()[0]; // manage object lifetime so target is not destroyed + auto dt = t.GetDistributionType(); + auto& v = t.GetDistributionRange(); + + VERIFY_ARE_EQUAL(dt, DistributionType::Percent); + VERIFY_ARE_EQUAL(v[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(v[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(v[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(v[0]._dst.second, (UINT64) 10); + VERIFY_ARE_EQUAL(v[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(v[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(v[1]._dst.first, (UINT64) 10); + VERIFY_ARE_EQUAL(v[1]._dst.second, (UINT64) 10); + VERIFY_ARE_EQUAL(v[2]._src, (UINT64) 20); /// + VERIFY_ARE_EQUAL(v[2]._span, (UINT64) 0); + VERIFY_ARE_EQUAL(v[2]._dst.first, (UINT64) 20); + VERIFY_ARE_EQUAL(v[2]._dst.second, (UINT64) 10); + VERIFY_ARE_EQUAL(v[3]._src, (UINT64) 20); /// + VERIFY_ARE_EQUAL(v[3]._span, (UINT64) 80); + VERIFY_ARE_EQUAL(v[3]._dst.first, (UINT64) 30); + VERIFY_ARE_EQUAL(v[3]._dst.second, (UINT64) 70); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs10/1G:10/1G:0/100G", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + + auto t = profile.GetTimeSpans()[0].GetTargets()[0]; + auto dt = t.GetDistributionType(); + auto& v = t.GetDistributionRange(); + + VERIFY_ARE_EQUAL(dt, DistributionType::Absolute); + VERIFY_ARE_EQUAL(v[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(v[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(v[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(v[0]._dst.second, (UINT64) 1*GB); + VERIFY_ARE_EQUAL(v[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(v[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(v[1]._dst.first, (UINT64) 1*GB); + VERIFY_ARE_EQUAL(v[1]._dst.second, (UINT64) 1*GB); + VERIFY_ARE_EQUAL(v[2]._src, (UINT64) 20); /// + VERIFY_ARE_EQUAL(v[2]._span, (UINT64) 0); + VERIFY_ARE_EQUAL(v[2]._dst.first, (UINT64) 2*GB); + VERIFY_ARE_EQUAL(v[2]._dst.second, (UINT64) 100*GB); + VERIFY_ARE_EQUAL(v[3]._src, (UINT64) 20); /// + VERIFY_ARE_EQUAL(v[3]._span, (UINT64) 80); + VERIFY_ARE_EQUAL(v[3]._dst.first, (UINT64) 102*GB); + VERIFY_ARE_EQUAL(v[3]._dst.second, (UINT64) 0); + } + + // valid with mixed load + { + Profile profile; + const char *argv[] = { "foo", "-rdpct90/10", "-rs50", "-r8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // + // Negative cases + // + + // not valid with sequential load + { + Profile profile; + const char *argv[] = { "foo", "-rdpct90/10", "-s", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdpct90/10", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // no/invalid distribution + { + Profile profile; + const char *argv[] = { "foo", "-rd", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdfoo", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdfoo10/1G:10/1G:0/100G", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // bad ints in first pos + { + Profile profile; + const char *argv[] = { "foo", "-rdpctBAD", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabsBAD", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // good int, no sep + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs10", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // good int, bad sep + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10[", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs10[", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // good int, good sep, no int + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10/", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs10/", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // good int, good sep, bad int + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10/BAD", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs10/BAD", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // good int, good sep, good int, bad sep + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10/10[", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs10/10g[", "-r", "testfile.dat" }; // detail - abs range > blocksize in order to be OK + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // good int, good sep, good int, good sep, no int + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10/10:", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs10/10g:", "-r", "testfile.dat" }; // detail - abs range > blocksize in order to be OK + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // good int, good sep, good int, good sep, bad int + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10/10:BAD", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs10/10g:BAD", "-r", "testfile.dat" }; // detail - abs range > blocksize in order to be OK + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // + // Negative cases for IO% + // + + // a single pct cannot be > 100, and cannot sum > 100 + { + Profile profile; + const char *argv[] = { "foo", "-rdpct101/10", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdpct60/10:60/10", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs101/10g", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs60/10g:60/10g", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // target% cannot be covered before IO% is covered + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10/50:10/50", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // target% cannot be > 100 + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10/50:10/60", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // + // Negative cases for Target%/Size + // + + // a target%/size cannot be zero (first/second positions) + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10/0", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdpct60/10:10/0", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs10/0", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs60/10g:10/0", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // + // Negative cases for abs + // + + // abs range must be >= blocksize + { + Profile profile; + const char *argv[] = { "foo", "-rdabs10/10", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + } + + void CmdLineParserUnitTests::TestParseCmdLineResultOutput() + { + // Cases for combinations of -R[p][text|xml] + + CmdLineParser p; + struct Synchronization s = {}; + + char *aType[] = { "text", "xml" }; + vector vType(aType, &aType[0] + _countof(aType)); + char *aProfile[] = { "-R", "-Rp" }; + vector vProfile(aProfile, &aProfile[0] + _countof(aProfile)); + + // combinations of p/text|xml OK + for (auto ty : vType) + { + for (auto pr : vProfile) + { + string str = pr; + str += ty; + + Profile profile; + const char *argv[] = { "foo", str.c_str(), "testfile.dat" }; + fprintf(stderr, "case: %s\n", str.c_str()); + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + + if (pr[2] == 'p') + { + VERIFY_IS_TRUE(profile.GetProfileOnly()); + } + else + { + VERIFY_IS_FALSE(profile.GetProfileOnly()); + } + + if (*ty == 't') + { + VERIFY_ARE_EQUAL(ResultsFormat::Text, profile.GetResultsFormat()); + } + else + { + VERIFY_ARE_EQUAL(ResultsFormat::Xml, profile.GetResultsFormat()); + } + } + } + } + + void CmdLineParserUnitTests::TestParseCmdLineTargetPosition() + { + // coverage for positioning of targets and parameters + + CmdLineParser p; + struct Synchronization s = {}; + + // in order - this obviously duplicates all the other normal cases but, to + // document it in place against the negative cases, repeat. + { + Profile profile; + const char *argv[] = { "foo", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-r", "testfile.dat" , "testfile2.dat"}; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // out of order - parameter follows one or more targets + { + Profile profile; + const char *argv[] = { "foo", "testfile.dat" , "-r"}; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "testfile.dat" , "testfile2.dat", "-r"}; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // out of order - in between + { + Profile profile; + const char *argv[] = { "foo", "testfile.dat" , "-r", "testfile2.dat"}; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + } +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/UnitTests/CmdLineParser/CmdLineParser.UnitTests.h b/CristalDiskMark/source/diskspd22/UnitTests/CmdLineParser/CmdLineParser.UnitTests.h new file mode 100644 index 0000000..d31cae4 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/CmdLineParser/CmdLineParser.UnitTests.h @@ -0,0 +1,117 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#include "WexTestClass.h" + +namespace UnitTests +{ + BEGIN_MODULE() + MODULE_PROPERTY(L"Feature", L"CmdLineParser") + END_MODULE() + + MODULE_SETUP(ModuleSetup); + MODULE_CLEANUP(ModuleCleanup); + + class CmdLineParserUnitTests : public WEX::TestClass + { + private: + void VerifyParseCmdLineDisableAllCache(Profile &profile); + void VerifyParseCmdLineMappedIO(Profile &profile, MemoryMappedIoFlushMode FlushMode); + void VerifyParseCmdLineAccessHints(Profile &profile, bool RandomAccess, bool SequentialScan, bool TemporaryFile); + + public: + TEST_CLASS(CmdLineParserUnitTests) + + TEST_CLASS_SETUP(ClassSetup); + TEST_CLASS_CLEANUP(ClassCleanup); + + TEST_METHOD_SETUP(MethodSetup); + TEST_METHOD_CLEANUP(MethodCleanup); + + TEST_METHOD(Test_GetSizeInBytes); + TEST_METHOD(TestGetRandomDataWriteBufferData); + TEST_METHOD(TestParseCmdLine); + TEST_METHOD(TestParseCmdLineAssignAffinity); + TEST_METHOD(TestParseCmdLineBlockSize); + TEST_METHOD(TestParseCmdLineBufferedWriteThrough); + TEST_METHOD(TestParseCmdLineBurstSizeAndThinkTime); + TEST_METHOD(TestParseCmdLineConflictingCacheModes); + TEST_METHOD(TestParseCmdLineCreateFileAndMaxFileSize); + TEST_METHOD(TestParseCmdLineDisableAffinity); + TEST_METHOD(TestParseCmdLineDisableAffinityConflict); + TEST_METHOD(TestParseCmdLineDisableAllCacheMode1); + TEST_METHOD(TestParseCmdLineDisableAllCacheMode2); + TEST_METHOD(TestParseCmdLineDisableLocalCache); + TEST_METHOD(TestParseCmdLineDisableOSCache); + TEST_METHOD(TestParseCmdLineDurationAndProgress); + TEST_METHOD(TestParseCmdLineEtwDISK_IO); + TEST_METHOD(TestParseCmdLineEtwIMAGE_LOAD); + TEST_METHOD(TestParseCmdLineEtwMEMORY_HARD_FAULTS); + TEST_METHOD(TestParseCmdLineEtwMEMORY_PAGE_FAULTS); + TEST_METHOD(TestParseCmdLineEtwNETWORK); + TEST_METHOD(TestParseCmdLineEtwPROCESS); + TEST_METHOD(TestParseCmdLineEtwREGISTRY); + TEST_METHOD(TestParseCmdLineEtwTHREAD); + TEST_METHOD(TestParseCmdLineEtwUseCycleCount); + TEST_METHOD(TestParseCmdLineEtwUsePagedMemory); + TEST_METHOD(TestParseCmdLineEtwUsePerfTimer); + TEST_METHOD(TestParseCmdLineEtwUseSystemTimer); + TEST_METHOD(TestParseCmdLineGroupAffinity); + TEST_METHOD(TestParseCmdLineHintFlag); + TEST_METHOD(TestParseCmdLineBaseMaxTarget); + TEST_METHOD(TestParseCmdLineInterlockedSequential); + TEST_METHOD(TestParseCmdLineInterlockedSequentialWithStride); + TEST_METHOD(TestParseCmdLineIOPriority); + TEST_METHOD(TestParseCmdLineMappedIO); + TEST_METHOD(TestParseCmdLineMeasureLatency); + TEST_METHOD(TestParseCmdLineOverlappedCountAndBaseOffset); + TEST_METHOD(TestParseCmdLineRandomIOAlignment); + TEST_METHOD(TestParseCmdLineRandomSequentialMixed); + TEST_METHOD(TestParseCmdLineRandomWriteBuffers); + TEST_METHOD(TestParseCmdLineRandSeed); + TEST_METHOD(TestParseCmdLineRandSeedGetTickCount); + TEST_METHOD(TestParseCmdLineResultOutput); + TEST_METHOD(TestParseCmdLineStrideSize); + TEST_METHOD(TestParseCmdLineTargetDistribution); + TEST_METHOD(TestParseCmdLineTargetPosition); + TEST_METHOD(TestParseCmdLineThreadsPerFileAndThreadStride); + TEST_METHOD(TestParseCmdLineThroughput); + TEST_METHOD(TestParseCmdLineTotalThreadCountAndThroughput); + TEST_METHOD(TestParseCmdLineTotalThreadCountAndTotalRequestCount); + TEST_METHOD(TestParseCmdLineUseCompletionRoutines); + TEST_METHOD(TestParseCmdLineUseLargePages); + TEST_METHOD(TestParseCmdLineUseParallelAsyncIO); + TEST_METHOD(TestParseCmdLineVerbose); + TEST_METHOD(TestParseCmdLineWarmupAndCooldown); + TEST_METHOD(TestParseCmdLineWriteBufferContentRandomNoFilePath); + TEST_METHOD(TestParseCmdLineWriteBufferContentRandomWithFilePath); + TEST_METHOD(TestParseCmdLineZeroWriteBuffers); + }; +} diff --git a/CristalDiskMark/source/diskspd22/UnitTests/CmdLineParser/CmdLineParser.rc b/CristalDiskMark/source/diskspd22/UnitTests/CmdLineParser/CmdLineParser.rc new file mode 100644 index 0000000..53d2e74 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/CmdLineParser/CmdLineParser.rc @@ -0,0 +1,17 @@ +#include +#include "Version.h" + +#include + +#define VER_FILETYPE VFT_APP +#define VER_FILESUBTYPE VFT2_UNKNOWN +#define VER_FILEDESCRIPTION_STR "DiskSpd Storage Performance Tool" +#define VER_INTERNALNAME_STR "CmdLineParser.UnitTests.dll" + +#undef VER_PRODUCTVERSION +#define VER_PRODUCTVERSION DISKSPD_MAJOR,DISKSPD_MINOR,DISKSPD_BUILD,DISKSPD_QFE + +#undef VER_PRODUCTVERSION_STR +#define VER_PRODUCTVERSION_STR DISKSPD_NUMERIC_VERSION_STRING + +#include "common.ver" diff --git a/CristalDiskMark/source/diskspd22/UnitTests/CmdLineParser/stdafx.h b/CristalDiskMark/source/diskspd22/UnitTests/CmdLineParser/stdafx.h new file mode 100644 index 0000000..883be10 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/CmdLineParser/stdafx.h @@ -0,0 +1,33 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + +#include + diff --git a/CristalDiskMark/source/diskspd22/UnitTests/Common/Common.UnitTests.cpp b/CristalDiskMark/source/diskspd22/UnitTests/Common/Common.UnitTests.cpp new file mode 100644 index 0000000..316cc34 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/Common/Common.UnitTests.cpp @@ -0,0 +1,1229 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "StdAfx.h" +#include "Common.UnitTests.h" +#include "Common.h" +#include + +using namespace WEX::TestExecution; +using namespace WEX::Logging; + +namespace UnitTests +{ + void PerfTimerUnitTests::Test_Freq() + { + VERIFY_IS_TRUE(PerfTimer::TIMER_FREQ > 0); + } + + void PerfTimerUnitTests::Test_GetTime() + { + VERIFY_IS_TRUE(PerfTimer::GetTime() > 0); + } + + void PerfTimerUnitTests::Test_PerfTimeToSeconds() + { + double d = PerfTimer::PerfTimeToSeconds(PerfTimer::TIMER_FREQ); + printf("tos %f %a ==? %f %a\n", d, d, 1.0, 1.0); + VERIFY_IS_TRUE(d == 1.0); + } + + void PerfTimerUnitTests::Test_PerfTimeToMilliseconds() + { + double d = PerfTimer::PerfTimeToMilliseconds(PerfTimer::TIMER_FREQ); + printf("toms %f %a ==? %f %a\n", d, d, 1000.0, 1000.0); + VERIFY_IS_TRUE(d == 1000.0); + } + + void PerfTimerUnitTests::Test_PerfTimeToMicroseconds() + { + double d = PerfTimer::PerfTimeToMicroseconds(PerfTimer::TIMER_FREQ); + printf("tous %f %a ==? %f %a\n", d, d, 1000000.0, 1000000.0); + VERIFY_IS_TRUE(d == 1000000.0); + } + + void PerfTimerUnitTests::Test_SecondsToPerfTime() + { + UINT64 u = PerfTimer::SecondsToPerfTime(1.0); + VERIFY_IS_TRUE(u == PerfTimer::TIMER_FREQ); + } + + void PerfTimerUnitTests::Test_MillisecondsToPerfTime() + { + UINT64 u = PerfTimer::MillisecondsToPerfTime(1000.0); + VERIFY_IS_TRUE(u == PerfTimer::TIMER_FREQ); + } + + void PerfTimerUnitTests::Test_MicrosecondsToPerfTime() + { + UINT64 u = PerfTimer::MicrosecondsToPerfTime(1000000.0); + VERIFY_IS_TRUE(u == PerfTimer::TIMER_FREQ); + } + + void HistogramUnitTests::Test_Empty() + { + Histogram h; + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)0); + } + + void HistogramUnitTests::Test_Add() + { + Histogram h; + h.Add(42); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)1); + VERIFY_ARE_EQUAL(h.GetSampleBuckets(), (unsigned)1); + + h.Add(42); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)2); + VERIFY_ARE_EQUAL(h.GetSampleBuckets(), (unsigned)1); + + h.Add(0); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)3); + VERIFY_ARE_EQUAL(h.GetSampleBuckets(), (unsigned)2); + + // seal/reset count + (void) h.GetMin(); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)3); + VERIFY_ARE_EQUAL(h.GetSampleBuckets(), (unsigned)2); + + h.Add(0); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)1); + VERIFY_ARE_EQUAL(h.GetSampleBuckets(), (unsigned)1); + + (void) h.GetMin(); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)1); + VERIFY_ARE_EQUAL(h.GetSampleBuckets(), (unsigned)1); + } + + void HistogramUnitTests::Test_Clear() + { + Histogram h; + h.Add(42); + h.Clear(); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)0); + } + + void HistogramUnitTests::Test_MinMax() + { + // use unsigned here for the sake of a compact empty "min" + // signed would be ~0 as negative int + Histogram h; + h.Add(1); + h.Add(3); + VERIFY_ARE_EQUAL(h.GetMin(), (unsigned)1); + VERIFY_ARE_EQUAL(h.GetMax(), (unsigned)3); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)2); + + // seal/reset + h.Add(2); + VERIFY_ARE_EQUAL(h.GetMin(), (unsigned)2); + VERIFY_ARE_EQUAL(h.GetMax(), (unsigned)2); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)1); + + // empty case + h.Clear(); + VERIFY_ARE_EQUAL(h.GetMin(), (unsigned)0); + VERIFY_ARE_EQUAL(h.GetMax(), (unsigned)0); + } + + void HistogramUnitTests::Test_GetPercentile() + { + Histogram h; + h.Add(1); + h.Add(2); + h.Add(3); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)3); + VERIFY_ARE_EQUAL(h.GetPercentile(0.0), 1); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 2); + VERIFY_ARE_EQUAL(h.GetPercentile(1.0), 3); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)3); + + // single sample buckets + for (int i = 1; i < 100; i++) + { + h.Add(i); + } + // double query at same val, forward, back and again + // stresses iterator save correctness + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)99); + VERIFY_ARE_EQUAL(h.GetPercentile(0.0), 1); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 50); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 50); + VERIFY_ARE_EQUAL(h.GetPercentile(0.6), 60); + VERIFY_ARE_EQUAL(h.GetPercentile(0.1), 10); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 50); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 50); + VERIFY_ARE_EQUAL(h.GetPercentile(0.6), 60); + VERIFY_ARE_EQUAL(h.GetPercentile(1.0), 99); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)99); + + // multiple sample buckets - all same (2) + for (int i = 1; i < 100; i++) + { + h.Add(i); + h.Add(i); + } + // double query at same val, forward, back and again + // stresses iterator save correctness + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)198); + VERIFY_ARE_EQUAL(h.GetPercentile(0.0), 1); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 50); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 50); + VERIFY_ARE_EQUAL(h.GetPercentile(0.6), 60); + VERIFY_ARE_EQUAL(h.GetPercentile(0.1), 10); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 50); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 50); + VERIFY_ARE_EQUAL(h.GetPercentile(0.6), 60); + VERIFY_ARE_EQUAL(h.GetPercentile(1.0), 99); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)198); + + // multiple sample buckets - extra weights on low end shift things lower + for (int i = 1; i < 100; i++) + { + h.Add(i); + + if (i < 50) + { + h.Add(i); + } + } + // double query at same val, forward, back and again + // stresses iterator save correctness + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)148); + VERIFY_ARE_EQUAL(h.GetPercentile(0.0), 1); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 37); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 37); + VERIFY_ARE_EQUAL(h.GetPercentile(0.6), 45); + VERIFY_ARE_EQUAL(h.GetPercentile(0.1), 8); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 37); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 37); + VERIFY_ARE_EQUAL(h.GetPercentile(0.6), 45); + VERIFY_ARE_EQUAL(h.GetPercentile(1.0), 99); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)148); + } + + void HistogramUnitTests::Test_GetMean() + { + Histogram h; + h.Add(2); + h.Add(4); + VERIFY_ARE_EQUAL(h.GetMean(), 3); + } + + void HistogramUnitTests::Test_Merge() + { + Histogram h1; + h1.Add(1); + + Histogram h2; + h2.Add(2); + + h1.Merge(h2); + + VERIFY_ARE_EQUAL(h1.GetSampleSize(), (unsigned)2); + } + + void IoBucketizerUnitTests::Test_Empty() + { + IoBucketizer b; + VERIFY_ARE_EQUAL(b.GetNumberOfValidBuckets(), (size_t)0); + } + + void IoBucketizerUnitTests::Test_Add() + { + IoBucketizer b; + b.Initialize(10, 4); + + b.Add(5, 1); + b.Add(8, 2); + VERIFY_ARE_EQUAL(b.GetNumberOfValidBuckets(), (size_t)1); + + b.Add(15, 3); + VERIFY_ARE_EQUAL(b.GetNumberOfValidBuckets(), (size_t)2); + + b.Add(18, 5); + VERIFY_ARE_EQUAL(b.GetNumberOfValidBuckets(), (size_t)2); + + b.Add(45, 4); + VERIFY_ARE_EQUAL(b.GetNumberOfValidBuckets(), (size_t)4); + + VERIFY_ARE_EQUAL(b.GetIoBucketCount(0), (unsigned int)2); + VERIFY_ARE_EQUAL(b.GetIoBucketMinDurationUsec(0), 1L); + VERIFY_ARE_EQUAL(b.GetIoBucketMaxDurationUsec(0), 2L); + VERIFY_ARE_EQUAL(b.GetIoBucketAvgDurationUsec(0), 1.5L); + VERIFY_ARE_EQUAL(b.GetIoBucketDurationStdDevUsec(0), 0.5L); + VERIFY_ARE_EQUAL(b.GetIoBucketCount(1), (unsigned int)2); + VERIFY_ARE_EQUAL(b.GetIoBucketMinDurationUsec(1), 3L); + VERIFY_ARE_EQUAL(b.GetIoBucketMaxDurationUsec(1), 5L); + VERIFY_ARE_EQUAL(b.GetIoBucketAvgDurationUsec(1), 4L); + VERIFY_ARE_EQUAL(b.GetIoBucketDurationStdDevUsec(1), 1L); + VERIFY_ARE_EQUAL(b.GetIoBucketCount(2), (unsigned int)0); + VERIFY_ARE_EQUAL(b.GetIoBucketMinDurationUsec(2), 0); + VERIFY_ARE_EQUAL(b.GetIoBucketMaxDurationUsec(2), 0); + VERIFY_ARE_EQUAL(b.GetIoBucketAvgDurationUsec(2), 0); + VERIFY_ARE_EQUAL(b.GetIoBucketDurationStdDevUsec(2), 0); + VERIFY_ARE_EQUAL(b.GetIoBucketCount(3), (unsigned int)0); + VERIFY_ARE_EQUAL(b.GetIoBucketMinDurationUsec(3), 0); + VERIFY_ARE_EQUAL(b.GetIoBucketMaxDurationUsec(3), 0); + VERIFY_ARE_EQUAL(b.GetIoBucketAvgDurationUsec(3), 0); + VERIFY_ARE_EQUAL(b.GetIoBucketDurationStdDevUsec(3), 0); + } + + void IoBucketizerUnitTests::Test_Merge() + { + IoBucketizer b1; + IoBucketizer b2; + b1.Initialize(10, 3); + b2.Initialize(10, 3); + + // b1 buckets: 2,0,1 + b1.Add(0, 0); + b1.Add(1, 0); + b1.Add(20, 0); + b1.Add(30, 0); + + VERIFY_ARE_EQUAL(b1.GetNumberOfValidBuckets(), (size_t)3); + VERIFY_ARE_EQUAL(b1.GetIoBucketCount(0), (unsigned int)2); + VERIFY_ARE_EQUAL(b1.GetIoBucketCount(1), (unsigned int)0); + VERIFY_ARE_EQUAL(b1.GetIoBucketCount(2), (unsigned int)1); + + // b2 buckets: 1,3 + b2.Add(0, 0); + b2.Add(10, 0); + b2.Add(11, 0); + b2.Add(12, 0); + + VERIFY_ARE_EQUAL(b2.GetNumberOfValidBuckets(), (size_t)2); + VERIFY_ARE_EQUAL(b2.GetIoBucketCount(0), (unsigned int)1); + VERIFY_ARE_EQUAL(b2.GetIoBucketCount(1), (unsigned int)3); + + b1.Merge(b2); + + // Merged buckets: 3,3,1 + VERIFY_ARE_EQUAL(b1.GetNumberOfValidBuckets(), (size_t)3); + VERIFY_ARE_EQUAL(b1.GetIoBucketCount(0), (unsigned int)3); + VERIFY_ARE_EQUAL(b1.GetIoBucketCount(1), (unsigned int)3); + VERIFY_ARE_EQUAL(b1.GetIoBucketCount(2), (unsigned int)1); + + // Source unchanged. + VERIFY_ARE_EQUAL(b2.GetNumberOfValidBuckets(), (size_t)2); + VERIFY_ARE_EQUAL(b2.GetIoBucketCount(0), (unsigned int)1); + VERIFY_ARE_EQUAL(b2.GetIoBucketCount(1), (unsigned int)3); + + // Merge into empty bucketizer + IoBucketizer b3; + + // Its empty. + VERIFY_ARE_EQUAL(b3.GetNumberOfValidBuckets(), (size_t)0); + + b3.Merge(b1); + + // Merged buckets: 3,3,1 + VERIFY_ARE_EQUAL(b3.GetNumberOfValidBuckets(), (size_t)3); + VERIFY_ARE_EQUAL(b3.GetIoBucketCount(0), (unsigned int)3); + VERIFY_ARE_EQUAL(b3.GetIoBucketCount(1), (unsigned int)3); + VERIFY_ARE_EQUAL(b3.GetIoBucketCount(2), (unsigned int)1); + } + + void IoBucketizerUnitTests::Test_GetStandardDeviation() + { + IoBucketizer b; + b.Initialize(10, 2); + + // b buckets: 1,2 + b.Add(0, 0); + b.Add(10, 0); + b.Add(11, 0); + b.Add(20, 0); + + // Standard deviation from valid buckets (the first two) is STDDEV(1,2) = 0.5 + VERIFY_ARE_EQUAL(b.GetStandardDeviationIOPS(), 0.5L); + } + + void ProfileUnitTests::Test_GetXmlEmptyProfile() + { + Profile profile; + string sXml = profile.GetXml(0); + //printf("'%s'\n", sXml.c_str()); + VERIFY_IS_TRUE(sXml == "\n" + " 0\n" + " text\n" + " false\n" + " \n" + " \n" + "\n"); + } + + void ProfileUnitTests::Test_GetXmlPrecreateFilesUseMaxSize() + { + Profile profile; + profile.SetPrecreateFiles(PrecreateFiles::UseMaxSize); + string sXml = profile.GetXml(0); + //printf("'%s'\n", sXml.c_str()); + VERIFY_IS_TRUE(sXml == "\n" + " 0\n" + " text\n" + " false\n" + " UseMaxSize\n" + " \n" + " \n" + "\n"); + } + + void ProfileUnitTests::Test_GetXmlPrecreateFilesOnlyFilesWithConstantSizes() + { + Profile profile; + profile.SetPrecreateFiles(PrecreateFiles::OnlyFilesWithConstantSizes); + string sXml = profile.GetXml(0); + //printf("'%s'\n", sXml.c_str()); + VERIFY_IS_TRUE(sXml == "\n" + " 0\n" + " text\n" + " false\n" + " CreateOnlyFilesWithConstantSizes\n" + " \n" + " \n" + "\n"); + } + + void ProfileUnitTests::Test_GetXmlPrecreateFilesOnlyFilesWithConstantOrZeroSizes() + { + Profile profile; + profile.SetPrecreateFiles(PrecreateFiles::OnlyFilesWithConstantOrZeroSizes); + string sXml = profile.GetXml(0); + //printf("'%s'\n", sXml.c_str()); + VERIFY_IS_TRUE(sXml == "\n" + " 0\n" + " text\n" + " false\n" + " CreateOnlyFilesWithConstantOrZeroSizes\n" + " \n" + " \n" + "\n"); + } + + void ProfileUnitTests::Test_MarkFilesAsCreated() + { + Target target1; + target1.SetPath("file1.txt"); + + Target target2; + target2.SetPath("file2.txt"); + + Target target3; + target3.SetPath("file1.txt"); + + Target target4; + target4.SetPath("file3.txt"); + + Target target5; + target5.SetPath("file2.txt"); + + Target target6; + target6.SetPath("file2.txt"); + + TimeSpan timeSpan1; + timeSpan1.AddTarget(target1); + timeSpan1.AddTarget(target2); + + TimeSpan timeSpan2; + timeSpan2.AddTarget(target3); + timeSpan2.AddTarget(target4); + timeSpan2.AddTarget(target5); + timeSpan2.AddTarget(target6); + + Profile profile; + profile.AddTimeSpan(timeSpan1); + profile.AddTimeSpan(timeSpan2); + + vector vFiles; + vFiles.push_back("file1.txt"); + vFiles.push_back("file2.txt"); + + VERIFY_IS_FALSE(profile._vTimeSpans[0]._vTargets[0]._fPrecreated); + VERIFY_IS_FALSE(profile._vTimeSpans[0]._vTargets[1]._fPrecreated); + VERIFY_IS_FALSE(profile._vTimeSpans[1]._vTargets[0]._fPrecreated); + VERIFY_IS_FALSE(profile._vTimeSpans[1]._vTargets[1]._fPrecreated); + VERIFY_IS_FALSE(profile._vTimeSpans[1]._vTargets[2]._fPrecreated); + VERIFY_IS_FALSE(profile._vTimeSpans[1]._vTargets[3]._fPrecreated); + + profile.MarkFilesAsPrecreated(vFiles); + VERIFY_IS_TRUE(profile._vTimeSpans[0]._vTargets[0]._fPrecreated); + VERIFY_IS_TRUE(profile._vTimeSpans[0]._vTargets[1]._fPrecreated); + VERIFY_IS_TRUE(profile._vTimeSpans[1]._vTargets[0]._fPrecreated); + VERIFY_IS_FALSE(profile._vTimeSpans[1]._vTargets[1]._fPrecreated); + VERIFY_IS_TRUE(profile._vTimeSpans[1]._vTargets[2]._fPrecreated); + VERIFY_IS_TRUE(profile._vTimeSpans[1]._vTargets[3]._fPrecreated); + } + + void ProfileUnitTests::Test_Validate() + { + TimeSpan timeSpan; + Target target; + + target.SetBaseFileOffsetInBytes(1000); + target.SetBlockAlignmentInBytes(500); + target.SetBlockSizeInBytes(1000); + target.SetThreadStrideInBytes(5000); + timeSpan.AddTarget(target); + + Profile profile; + profile.AddTimeSpan(timeSpan); + + // thread stride errors if only one thread used (default) + // both the single spec assumption and full should behave the same + VERIFY_IS_FALSE(profile.Validate(true)); + VERIFY_IS_FALSE(profile.Validate(false)); + + profile._vTimeSpans[0].SetThreadCount(2); + VERIFY_IS_TRUE(profile.Validate(true)); + VERIFY_IS_TRUE(profile.Validate(false)); + + // now turning on interlocked sequential, fail since thread stride is set + profile._vTimeSpans[0]._vTargets[0].SetUseInterlockedSequential(true); + VERIFY_IS_FALSE(profile.Validate(true)); + VERIFY_IS_FALSE(profile.Validate(false)); + + profile._vTimeSpans[0]._vTargets[0].SetThreadStrideInBytes(0); + VERIFY_IS_TRUE(profile.Validate(true)); + VERIFY_IS_TRUE(profile.Validate(false)); + } + + void ProfileUnitTests::Test_ValidateSystem() + { + // processor topology validation for affinity assignments + // 2 group, 2 procs/group + SystemInformation system; + system.processorTopology._vProcessorGroupInformation.clear(); + system.processorTopology._vProcessorGroupInformation.emplace_back((WORD)0, (BYTE)2, (BYTE)2, (KAFFINITY)0x3); + system.processorTopology._vProcessorGroupInformation.emplace_back((WORD)1, (BYTE)2, (BYTE)2, (KAFFINITY)0x3); + + TimeSpan timeSpan; + Profile profile; + + // assign to each proc + profile.ClearTimeSpans(); + timeSpan.ClearAffinityAssignment(); + timeSpan.AddAffinityAssignment(0, 0); + timeSpan.AddAffinityAssignment(0, 1); + timeSpan.AddAffinityAssignment(1, 0); + timeSpan.AddAffinityAssignment(1, 1); + profile.AddTimeSpan(timeSpan); + VERIFY_IS_TRUE(profile.Validate(true, &system)); + + // shrink active mask + system.processorTopology._vProcessorGroupInformation.clear(); + system.processorTopology._vProcessorGroupInformation.emplace_back((WORD)0, (BYTE)2, (BYTE)2, (KAFFINITY)0x1); + system.processorTopology._vProcessorGroupInformation.emplace_back((WORD)1, (BYTE)2, (BYTE)2, (KAFFINITY)0x1); + + // fail assignment to inactive procs + VERIFY_IS_FALSE(profile.Validate(true, &system)); + + // shrink procs, still fail + system.processorTopology._vProcessorGroupInformation.clear(); + system.processorTopology._vProcessorGroupInformation.emplace_back((WORD)0, (BYTE)1, (BYTE)1, (KAFFINITY)0x1); + system.processorTopology._vProcessorGroupInformation.emplace_back((WORD)1, (BYTE)1, (BYTE)1, (KAFFINITY)0x1); + + // now fail + VERIFY_IS_FALSE(profile.Validate(true, &system)); + + // assign to low procs, and succeed + profile.ClearTimeSpans(); + timeSpan.ClearAffinityAssignment(); + timeSpan.AddAffinityAssignment(0, 0); + timeSpan.AddAffinityAssignment(1, 0); + profile.AddTimeSpan(timeSpan); + VERIFY_IS_TRUE(profile.Validate(true, &system)); + + // shrink groups + system.processorTopology._vProcessorGroupInformation.clear(); + system.processorTopology._vProcessorGroupInformation.emplace_back((WORD)0, (BYTE)1, (BYTE)1, (KAFFINITY)0x1); + + // now fail + VERIFY_IS_FALSE(profile.Validate(true, &system)); + + // assign to low proc, and succeed + profile.ClearTimeSpans(); + timeSpan.ClearAffinityAssignment(); + timeSpan.AddAffinityAssignment(0, 0); + profile.AddTimeSpan(timeSpan); + VERIFY_IS_TRUE(profile.Validate(true, &system)); + + // assign to invalid group + profile.ClearTimeSpans(); + timeSpan.ClearAffinityAssignment(); + timeSpan.AddAffinityAssignment(1, 0); + profile.AddTimeSpan(timeSpan); + VERIFY_IS_FALSE(profile.Validate(true, &system)); + } + + void TargetUnitTests::TestGetSetRandomDataWriteBufferSize() + { + Target t; + VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSize(), 0); + t.SetRandomDataWriteBufferSize(1234); + VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSize(), 1234); + } + + void TargetUnitTests::TestGetSetRandomDataWriteBufferSourcePath() + { + Target t; + VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSourcePath(), ""); + t.SetRandomDataWriteBufferSourcePath("x:\\foo\\bar.dat"); + VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSourcePath(), "x:\\foo\\bar.dat"); + } + + void TargetUnitTests::Test_TargetGetXmlWriteBufferContentSequential() + { + Target target; + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlWriteBufferContentZero() + { + Target target; + target.SetZeroWriteBuffers(true); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " \n" + " zero\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlWriteBufferContentRandomNoFilePath() + { + Target target; + target.SetRandomDataWriteBufferSize(224433); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " \n" + " random\n" + " \n" + " 224433\n" + " \n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlWriteBufferContentRandomWithFilePath() + { + Target target; + target.SetRandomDataWriteBufferSize(224433); + target.SetRandomDataWriteBufferSourcePath("x:\\foo\\bar.baz"); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " \n" + " random\n" + " \n" + " 224433\n" + " x:\\foo\\bar.baz\n" + " \n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlDisableAllCache() + { + Target target; + target.SetCacheMode(TargetCacheMode::DisableOSCache); + target.SetWriteThroughMode(WriteThroughMode::On); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " true\n" + " true\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlDisableLocalCache() + { + Target target; + target.SetCacheMode(TargetCacheMode::DisableLocalCache); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " true\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlDisableOSCache() + { + Target target; + target.SetCacheMode(TargetCacheMode::DisableOSCache); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " true\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlBufferedWriteThrough() + { + Target target; + target.SetWriteThroughMode(WriteThroughMode::On); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " true\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlMemoryMappedIo() + { + Target target; + target.SetMemoryMappedIoMode(MemoryMappedIoMode::On); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " true\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlMemoryMappedIoFlushModeViewOfFile() + { + Target target; + target.SetMemoryMappedIoMode(MemoryMappedIoMode::On); + target.SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::ViewOfFile); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " true\n" + " ViewOfFile\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlMemoryMappedIoFlushModeNonVolatileMemory() + { + Target target; + target.SetMemoryMappedIoMode(MemoryMappedIoMode::On); + target.SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::NonVolatileMemory); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " true\n" + " NonVolatileMemory\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlMemoryMappedIoFlushModeNonVolatileMemoryNoDrain() + { + Target target; + target.SetMemoryMappedIoMode(MemoryMappedIoMode::On); + target.SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " true\n" + " NonVolatileMemoryNoDrain\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlRandomAccessHint() + { + Target target; + target.SetRandomAccessHint(true); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " true\n" + " false\n" + " false\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlSequentialScanHint() + { + Target target; + target.SetSequentialScanHint(true); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " true\n" + " false\n" + " false\n" + " false\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlCombinedAccessHint() + { + Target target; + target.SetSequentialScanHint(true); + target.SetTemporaryFileHint(true); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " true\n" + " false\n" + " true\n" + " false\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_AllocateAndFillRandomDataWriteBuffer() + { + Random r; + Target t; + VERIFY_IS_FALSE(t.AllocateAndFillRandomDataWriteBuffer(&r)); + VERIFY_ARE_EQUAL(t._pRandomDataWriteBuffer, nullptr); + + size_t cb = 12345; + t.SetRandomDataWriteBufferSize(cb); + VERIFY_IS_TRUE(t.AllocateAndFillRandomDataWriteBuffer(&r)); + VERIFY_IS_TRUE(t._pRandomDataWriteBuffer != nullptr); + // see if the test crashes if we try to write to every byte of the buffer + + for (size_t i = 0; i < cb; i++) + { + t._pRandomDataWriteBuffer[i] = (i % 256); + } + + for (size_t i = 0; i < cb; i++) + { + if (t._pRandomDataWriteBuffer[i] != (i % 256)) + { + // don't call VERIFY_ARE_EQUAL on each item because it prints to the screen and makes the test take + // too long + VERIFY_IS_TRUE(false); + } + } + } + + void TargetUnitTests::Test_AllocateAndFillRandomDataWriteBufferFromFile() + { + char szTempDirPath[MAX_PATH] = {}; + DWORD cch = GetTempPathA(_countof(szTempDirPath), szTempDirPath); + VERIFY_IS_TRUE(cch != 0); + string sTempFilePath(szTempDirPath); + sTempFilePath += "diskspd-random-data-file.dat"; + DeleteFileA(sTempFilePath.c_str()); + + printf("path: '%s'\n", sTempFilePath.c_str()); + FILE *pFile; + fopen_s(&pFile, sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + char buffer[256]; + for (int i = 0; i < 256; i++) + { + buffer[i] = static_cast(0xFF - i); + } + VERIFY_ARE_EQUAL(fwrite(buffer, sizeof(buffer), 1, pFile), (size_t)1); + fclose(pFile); + pFile = nullptr; + + Random r; + Target t; + size_t cbBuffer = 1024 * 1024; + t.SetRandomDataWriteBufferSize(cbBuffer); + t.SetRandomDataWriteBufferSourcePath(sTempFilePath.c_str()); + VERIFY_IS_TRUE(t.AllocateAndFillRandomDataWriteBuffer(&r)); + VERIFY_IS_TRUE(t._pRandomDataWriteBuffer != nullptr); + for (size_t i = 0; i < cbBuffer; i++) + { + if (t._pRandomDataWriteBuffer[i] != (0xFF - (i % 256))) + { + // don't call VERIFY_ARE_EQUAL on each item because it prints to the screen and makes the test take + // too long + VERIFY_IS_TRUE(false); + } + } + + DeleteFileA(sTempFilePath.c_str()); + } + + void ThreadParametersUnitTests::Test_AllocateAndFillBufferForTarget() + { + TimeSpan ts; + Target t; + Random r; + t.SetBlockSizeInBytes(12345); + t.SetRequestCount(12); + ThreadParameters tp; + tp.pTimeSpan = &ts; + tp.pRand = &r; + VERIFY_IS_TRUE(tp.AllocateAndFillBufferForTarget(t)); + + // see if the test crashes if we try to write to every byte of the buffer + size_t cb = t.GetBlockSizeInBytes() * t.GetRequestCount(); + for (size_t i = 0; i < cb; i++) + { + tp.vpDataBuffers[0][i] = (i % 256); + } + + for (size_t i = 0; i < cb; i++) + { + if (tp.vpDataBuffers[0][i] != (i % 256)) + { + // don't call VERIFY_ARE_EQUAL on each item because it prints to the screen and makes the test take + // too long + VERIFY_IS_TRUE(false); + } + } + } + + void TopologyUnitTests::Test_MaskCount() + { + ULONG kaff_bits = sizeof(KAFFINITY) * 8; + + // a complete enumeration could be interesting, but a nibble is enough to test the algorithm. + // take the given mask and its width (ordinal distance to the upper 1), shift it through the + // range of KAFFINITY to verify the popcnt is correct at all positions + // + // note that unique masks have msb/lsb set for all combinations of the interior bits. we don't + // test "10" (0x2) as a mask, because it's not unique - its the same as the first shift-up of + // "1" (0x1), etc. + + struct { + KAFFINITY mask; + ULONG width; + ULONG bits; + } tests[] = { // msb ... lsb + { 0x1, 1, 1 }, // 1 + { 0x3, 2, 2 }, // 11 + { 0x5, 3, 2 }, // 101 + { 0x7, 3, 3 }, // 111 + { 0x9, 4, 2 }, // 1001 + { 0xb, 4, 3 }, // 1011 + { 0xd, 4, 3 }, // 1101 + { 0xf, 4, 4 } // 1111 + }; + + for (const auto &test : tests) + { + KAFFINITY mask = test.mask; + for (ULONG i = 0; i < kaff_bits - test.width; i++) + { + VERIFY_ARE_EQUAL(ProcessorTopology::MaskCount(mask), test.bits); + mask <<= 1; + } + } + + // ... and a few explicit true/false + VERIFY_ARE_NOT_EQUAL(ProcessorTopology::MaskCount(0x0), (ULONG)1); + VERIFY_ARE_NOT_EQUAL(ProcessorTopology::MaskCount(0x3), (ULONG)0); + VERIFY_ARE_NOT_EQUAL(ProcessorTopology::MaskCount(0x5), (ULONG)3); + + VERIFY_ARE_EQUAL(ProcessorTopology::MaskCount(0x0), (ULONG)0); + VERIFY_ARE_EQUAL(ProcessorTopology::MaskCount(0x3), (ULONG)2); + VERIFY_ARE_EQUAL(ProcessorTopology::MaskCount(0x5), (ULONG)2); + + VERIFY_ARE_EQUAL(ProcessorTopology::MaskCount(0xffff), (ULONG)16); + VERIFY_ARE_EQUAL(ProcessorTopology::MaskCount(0xfeef), (ULONG)14); + VERIFY_ARE_EQUAL(ProcessorTopology::MaskCount(0xfeef00ff), (ULONG)22); + VERIFY_ARE_EQUAL(ProcessorTopology::MaskCount(0xfe0000ff), (ULONG)15); + VERIFY_ARE_EQUAL(ProcessorTopology::MaskCount(0x7e0000ff), (ULONG)14); + } +} diff --git a/CristalDiskMark/source/diskspd22/UnitTests/Common/Common.UnitTests.h b/CristalDiskMark/source/diskspd22/UnitTests/Common/Common.UnitTests.h new file mode 100644 index 0000000..31d26a0 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/Common/Common.UnitTests.h @@ -0,0 +1,130 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#include "WexTestClass.h" + +namespace UnitTests +{ + BEGIN_MODULE() + MODULE_PROPERTY(L"Feature", L"Common") + END_MODULE() + + class PerfTimerUnitTests : public WEX::TestClass + { + public: + TEST_CLASS(PerfTimerUnitTests); + TEST_METHOD(Test_Freq); + TEST_METHOD(Test_GetTime); + TEST_METHOD(Test_PerfTimeToSeconds); + TEST_METHOD(Test_PerfTimeToMilliseconds); + TEST_METHOD(Test_PerfTimeToMicroseconds); + TEST_METHOD(Test_SecondsToPerfTime); + TEST_METHOD(Test_MillisecondsToPerfTime); + TEST_METHOD(Test_MicrosecondsToPerfTime); + }; + + class HistogramUnitTests : public WEX::TestClass + { + public: + TEST_CLASS(HistogramUnitTests); + TEST_METHOD(Test_Empty); + TEST_METHOD(Test_Add); + TEST_METHOD(Test_Clear); + TEST_METHOD(Test_MinMax); + TEST_METHOD(Test_GetPercentile); + TEST_METHOD(Test_GetMean); + TEST_METHOD(Test_Merge); + }; + + class IoBucketizerUnitTests : public WEX::TestClass + { + public: + TEST_CLASS(IoBucketizerUnitTests); + TEST_METHOD(Test_Empty); + TEST_METHOD(Test_Add); + TEST_METHOD(Test_Merge); + TEST_METHOD(Test_GetStandardDeviation); + }; + + class ProfileUnitTests : public WEX::TestClass + { + public: + TEST_CLASS(ProfileUnitTests); + TEST_METHOD(Test_GetXmlEmptyProfile); + TEST_METHOD(Test_GetXmlPrecreateFilesUseMaxSize); + TEST_METHOD(Test_GetXmlPrecreateFilesOnlyFilesWithConstantSizes); + TEST_METHOD(Test_GetXmlPrecreateFilesOnlyFilesWithConstantOrZeroSizes); + TEST_METHOD(Test_MarkFilesAsCreated); + TEST_METHOD(Test_Validate); + TEST_METHOD(Test_ValidateSystem); + }; + + class TargetUnitTests : public WEX::TestClass + { + public: + TEST_CLASS(TargetUnitTests); + TEST_METHOD(TestGetSetRandomDataWriteBufferSize); + TEST_METHOD(TestGetSetRandomDataWriteBufferSourcePath); + TEST_METHOD(Test_TargetGetXmlWriteBufferContentSequential); + TEST_METHOD(Test_TargetGetXmlWriteBufferContentZero); + TEST_METHOD(Test_TargetGetXmlWriteBufferContentRandomNoFilePath); + TEST_METHOD(Test_TargetGetXmlWriteBufferContentRandomWithFilePath); + TEST_METHOD(Test_TargetGetXmlDisableAllCache); + TEST_METHOD(Test_TargetGetXmlDisableLocalCache); + TEST_METHOD(Test_TargetGetXmlDisableOSCache); + TEST_METHOD(Test_TargetGetXmlBufferedWriteThrough); + TEST_METHOD(Test_TargetGetXmlMemoryMappedIo); + TEST_METHOD(Test_TargetGetXmlMemoryMappedIoFlushModeViewOfFile); + TEST_METHOD(Test_TargetGetXmlMemoryMappedIoFlushModeNonVolatileMemory); + TEST_METHOD(Test_TargetGetXmlMemoryMappedIoFlushModeNonVolatileMemoryNoDrain); + TEST_METHOD(Test_TargetGetXmlRandomAccessHint); + TEST_METHOD(Test_TargetGetXmlSequentialScanHint); + TEST_METHOD(Test_TargetGetXmlCombinedAccessHint); + TEST_METHOD(Test_AllocateAndFillRandomDataWriteBuffer); + TEST_METHOD(Test_AllocateAndFillRandomDataWriteBufferFromFile); + }; + + class ThreadParametersUnitTests : public WEX::TestClass + { + public: + TEST_CLASS(ThreadParametersUnitTests); + TEST_METHOD(Test_AllocateAndFillBufferForTarget); + }; + + class TopologyUnitTests : public WEX::TestClass + { + public: + TEST_CLASS(TopologyUnitTests); + TEST_METHOD(Test_MaskCount); + }; +} + +// TODO: ThreadParameters::GetWriteBuffer +// TODO: Target::GetRandomDataWriteBuffer(); diff --git a/CristalDiskMark/source/diskspd22/UnitTests/Common/Common.rc b/CristalDiskMark/source/diskspd22/UnitTests/Common/Common.rc new file mode 100644 index 0000000..0c601ae --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/Common/Common.rc @@ -0,0 +1,17 @@ +#include +#include "Version.h" + +#include + +#define VER_FILETYPE VFT_APP +#define VER_FILESUBTYPE VFT2_UNKNOWN +#define VER_FILEDESCRIPTION_STR "DiskSpd Storage Performance Tool" +#define VER_INTERNALNAME_STR "Common.UnitTests.dll" + +#undef VER_PRODUCTVERSION +#define VER_PRODUCTVERSION DISKSPD_MAJOR,DISKSPD_MINOR,DISKSPD_BUILD,DISKSPD_QFE + +#undef VER_PRODUCTVERSION_STR +#define VER_PRODUCTVERSION_STR DISKSPD_NUMERIC_VERSION_STRING + +#include "common.ver" diff --git a/CristalDiskMark/source/diskspd22/UnitTests/Common/stdafx.h b/CristalDiskMark/source/diskspd22/UnitTests/Common/stdafx.h new file mode 100644 index 0000000..883be10 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/Common/stdafx.h @@ -0,0 +1,33 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + +#include + diff --git a/CristalDiskMark/source/diskspd22/UnitTests/IORequestGenerator/IORequestGenerator.UnitTests.cpp b/CristalDiskMark/source/diskspd22/UnitTests/IORequestGenerator/IORequestGenerator.UnitTests.cpp new file mode 100644 index 0000000..0fe43ac --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/IORequestGenerator/IORequestGenerator.UnitTests.cpp @@ -0,0 +1,1327 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "StdAfx.h" +#include "IORequestGenerator.UnitTests.h" +#include "Common.h" +#include "IORequestGenerator.h" +#include + +using namespace WEX::TestExecution; +using namespace WEX::Logging; + +namespace UnitTests +{ + void IORequestGeneratorUnitTests::Test_GetFilesToPrecreate1() + { + Target target1; + target1.SetPath("file1.txt"); + target1.SetFileSize(100); + + Target target2; + target2.SetPath("file2.txt"); + + Target target3; + target3.SetPath("file1.txt"); + target3.SetFileSize(150); + + Target target4; + target4.SetPath("file2.txt"); + target4.SetFileSize(200); + + Target target5; + target5.SetPath("file3.txt"); + + Target target6; + target6.SetPath("file4.txt"); + target6.SetFileSize(300); + + Target target7; + target7.SetPath("file4.txt"); + target7.SetFileSize(300); + + Target target8; + target8.SetPath("file5.txt"); + target8.SetFileSize(300); + + Target target9; + target9.SetPath("file5.txt"); + target9.SetFileSize(300); + target9.SetZeroWriteBuffers(true); + + TimeSpan timeSpan1; + TimeSpan timeSpan2; + timeSpan1.AddTarget(target1); + timeSpan1.AddTarget(target2); + timeSpan2.AddTarget(target3); + timeSpan2.AddTarget(target4); + timeSpan2.AddTarget(target5); + timeSpan2.AddTarget(target6); + timeSpan2.AddTarget(target7); + timeSpan2.AddTarget(target8); + timeSpan2.AddTarget(target9); + + Profile profile; + profile.AddTimeSpan(timeSpan1); + profile.AddTimeSpan(timeSpan2); + + { + profile.SetPrecreateFiles(PrecreateFiles::UseMaxSize); + IORequestGenerator io; + vector v = io._GetFilesToPrecreate(profile); + VERIFY_ARE_EQUAL(v.size(), (size_t)3); + + VERIFY_ARE_EQUAL(v[0].sPath, "file1.txt"); + VERIFY_ARE_EQUAL(v[0].fZeroWriteBuffers, false); + VERIFY_ARE_EQUAL(v[0].ullFileSize, 150); + + VERIFY_ARE_EQUAL(v[1].sPath, "file2.txt"); + VERIFY_ARE_EQUAL(v[1].fZeroWriteBuffers, false); + VERIFY_ARE_EQUAL(v[1].ullFileSize, 200); + + VERIFY_ARE_EQUAL(v[2].sPath, "file4.txt"); + VERIFY_ARE_EQUAL(v[2].fZeroWriteBuffers, false); + VERIFY_ARE_EQUAL(v[2].ullFileSize, 300); + } + + { + profile.SetPrecreateFiles(PrecreateFiles::OnlyFilesWithConstantSizes); + IORequestGenerator io; + vector v = io._GetFilesToPrecreate(profile); + VERIFY_ARE_EQUAL(v.size(), (size_t)1); + + VERIFY_ARE_EQUAL(v[0].sPath, "file4.txt"); + VERIFY_ARE_EQUAL(v[0].fZeroWriteBuffers, false); + VERIFY_ARE_EQUAL(v[0].ullFileSize, 300); + } + + { + profile.SetPrecreateFiles(PrecreateFiles::OnlyFilesWithConstantOrZeroSizes); + IORequestGenerator io; + vector v = io._GetFilesToPrecreate(profile); + VERIFY_ARE_EQUAL(v.size(), (size_t)2); + + VERIFY_ARE_EQUAL(v[0].sPath, "file2.txt"); + VERIFY_ARE_EQUAL(v[0].fZeroWriteBuffers, false); + VERIFY_ARE_EQUAL(v[0].ullFileSize, 200); + + VERIFY_ARE_EQUAL(v[1].sPath, "file4.txt"); + VERIFY_ARE_EQUAL(v[1].fZeroWriteBuffers, false); + VERIFY_ARE_EQUAL(v[1].ullFileSize, 300); + } + + } + + void IORequestGeneratorUnitTests::Test_GetFilesToPrecreate2() + { + Target target1; + target1.SetPath("file1.txt"); + target1.SetFileSize(100); + + Target target2; + target2.SetPath("file2.txt"); + target2.SetFileSize(160); + + Target target3; + target3.SetPath("file1.txt"); + target3.SetFileSize(150); + + Target target4; + target4.SetPath("file2.txt"); + target4.SetFileSize(200); + + + TimeSpan timeSpan1; + TimeSpan timeSpan2; + timeSpan1.AddTarget(target1); + timeSpan1.AddTarget(target2); + timeSpan2.AddTarget(target3); + timeSpan2.AddTarget(target4); + + Profile profile; + profile.AddTimeSpan(timeSpan1); + profile.AddTimeSpan(timeSpan2); + + profile.SetPrecreateFiles(PrecreateFiles::UseMaxSize); + IORequestGenerator io; + vector v = io._GetFilesToPrecreate(profile); + VERIFY_ARE_EQUAL(v.size(), (size_t)2); + + VERIFY_ARE_EQUAL(v[0].sPath, "file1.txt"); + VERIFY_ARE_EQUAL(v[0].fZeroWriteBuffers, false); + VERIFY_ARE_EQUAL(v[0].ullFileSize, 150); + + VERIFY_ARE_EQUAL(v[1].sPath, "file2.txt"); + VERIFY_ARE_EQUAL(v[1].fZeroWriteBuffers, false); + VERIFY_ARE_EQUAL(v[1].ullFileSize, 200); + } + + void IORequestGeneratorUnitTests::Test_GetFilesToPrecreateConstantSizes() + { + Target target1; + target1.SetPath("file1.txt"); + + Target target2; + target2.SetPath("file1.txt"); + target2.SetFileSize(160); + + TimeSpan timeSpan1; + timeSpan1.AddTarget(target1); + timeSpan1.AddTarget(target2); + + Profile profile; + profile.AddTimeSpan(timeSpan1); + + profile.SetPrecreateFiles(PrecreateFiles::OnlyFilesWithConstantSizes); + IORequestGenerator io; + vector v = io._GetFilesToPrecreate(profile); + VERIFY_ARE_EQUAL(v.size(), (size_t)0); + } + + void IORequestGeneratorUnitTests::Test_GetFilesToPrecreateConstantOrZeroSizes() + { + Target target1; + target1.SetPath("file1.txt"); + + Target target2; + target2.SetPath("file1.txt"); + target2.SetFileSize(160); + + TimeSpan timeSpan1; + timeSpan1.AddTarget(target1); + timeSpan1.AddTarget(target2); + + Profile profile; + profile.AddTimeSpan(timeSpan1); + + profile.SetPrecreateFiles(PrecreateFiles::OnlyFilesWithConstantOrZeroSizes); + IORequestGenerator io; + vector v = io._GetFilesToPrecreate(profile); + VERIFY_ARE_EQUAL(v.size(), (size_t)1); + VERIFY_ARE_EQUAL(v[0].sPath, "file1.txt"); + VERIFY_ARE_EQUAL(v[0].fZeroWriteBuffers, false); + VERIFY_ARE_EQUAL(v[0].ullFileSize, 160); + } + + void IORequestGeneratorUnitTests::Test_GetFilesToPrecreateUseMaxSize() + { + Target target1; + target1.SetPath("file1.txt"); + + Target target2; + target2.SetPath("file1.txt"); + + TimeSpan timeSpan1; + timeSpan1.AddTarget(target1); + timeSpan1.AddTarget(target2); + + Profile profile; + profile.AddTimeSpan(timeSpan1); + + profile.SetPrecreateFiles(PrecreateFiles::UseMaxSize); + IORequestGenerator io; + vector v = io._GetFilesToPrecreate(profile); + VERIFY_ARE_EQUAL(v.size(), (size_t)0); + } + + void IORequestGeneratorUnitTests::Test_GetNextFileOffsetRandom() + { + Target target; + target.SetBaseFileOffsetInBytes(1000); + target.SetBlockAlignmentInBytes(500); + target.SetBlockSizeInBytes(1000); + target.SetRandomRatio(100); + + Random r; + ThreadParameters tp; + tp.pRand = &r; + tp.vTargets.push_back(target); + + TimeSpan timespan; + tp.pTimeSpan = ×pan; + + ThreadTargetState tts(&tp, 0, 3000); + IORequest ior(tp.pRand); + + ULARGE_INTEGER nextOffset; + + for( int i = 0; i < 10; ++i ) + { + tts.NextIORequest(ior); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + + VERIFY_IS_GREATER_THAN_OR_EQUAL(nextOffset.QuadPart, 1000); + VERIFY_IS_LESS_THAN_OR_EQUAL(nextOffset.QuadPart, 2000); + VERIFY_ARE_EQUAL(nextOffset.QuadPart % 500, 0); + } + } + + void IORequestGeneratorUnitTests::Test_GetNextFileOffsetSequential() + { + Target target; + target.SetBaseFileOffsetInBytes(1000); + target.SetBlockAlignmentInBytes(500); + target.SetBlockSizeInBytes(1000); + + Random r; + ThreadParameters tp; + tp.pRand = &r; + tp.vTargets.push_back(target); + + TimeSpan timespan; + tp.pTimeSpan = ×pan; + + ThreadTargetState tts(&tp, 0, 3000); + IORequest ior(tp.pRand); + + ULARGE_INTEGER nextOffset; + + tts.NextIORequest(ior); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, 1000); + tts.NextIORequest(ior); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, 1500); + tts.NextIORequest(ior); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, 2000); + tts.NextIORequest(ior); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, 1000); + } + + void IORequestGeneratorUnitTests::Test_GetNextFileOffsetInterlockedSequential() + { + Target target; + target.SetBaseFileOffsetInBytes(1000); + target.SetBlockAlignmentInBytes(500); + target.SetBlockSizeInBytes(1000); + target.SetUseInterlockedSequential(true); + + Random r; + ThreadParameters tp1; + ThreadParameters tp2; + tp1.pRand = &r; + tp2.pRand = &r; + + UINT64 sharedIndex = 0; + tp1.pullSharedSequentialOffsets = &sharedIndex; + tp2.pullSharedSequentialOffsets = &sharedIndex; + + tp1.vTargets.push_back(target); + tp2.vTargets.push_back(target); + + TimeSpan timespan; + timespan.SetThreadCount(2); + + tp1.pTimeSpan = ×pan; + tp2.pTimeSpan = ×pan; + + ThreadTargetState tts1(&tp1, 0, 3000); + ThreadTargetState tts2(&tp2, 0, 3000); + IORequest ior(tp1.pRand); + + ULARGE_INTEGER nextOffset; + + // begin at base + // thread 2 jumps in and continues the pattern (despite FIRST_OFFSET) + // note that blocksize is 1000, so we loop back at 2000 + tts1.NextIORequest(ior); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, 1000); + tts1.NextIORequest(ior); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, 1500); + tts2.NextIORequest(ior); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, 2000); + tts1.NextIORequest(ior); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, 1000); + tts2.NextIORequest(ior); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, 1500); + } + + void IORequestGeneratorUnitTests::Test_GetNextFileOffsetParallelAsyncIO() + { + Target target; + target.SetBaseFileOffsetInBytes(1000); + target.SetBlockAlignmentInBytes(500); + target.SetBlockSizeInBytes(1000); + target.SetUseParallelAsyncIO(true); + + Random r; + ThreadParameters tp; + tp.pRand = &r; + tp.vTargets.push_back(target); + + TimeSpan timespan; + tp.pTimeSpan = ×pan; + + ThreadTargetState tts(&tp, 0, 3000); + IORequest ior(tp.pRand); + + ULARGE_INTEGER nextOffset; + + { + UINT64 aOff[] = { 1000, 1500, 2000, 1000, 1500 }; + vector vOff(aOff, aOff + _countof(aOff)); + + tts.InitializeParallelAsyncIORequest(ior); + tts.NextIORequest(ior); + for (auto off : vOff) + { + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, off, L"case 1"); + tts.NextIORequest(ior); + } + } + } + + void IORequestGeneratorUnitTests::Test_GetThreadBaseFileOffset() + { + Random r; + ThreadParameters tp; + Target target; + + tp.pRand = &r; + target.SetBaseFileOffsetInBytes(1000); + target.SetBlockAlignmentInBytes(500); + target.SetBlockSizeInBytes(1000); + tp.vTargets.push_back(target); + tp.vTargets.push_back(target); + + UINT64 startingOffset; + + // normal sequential - both threads, each file at base + tp.vTargets[0].SetRandomRatio(0); + tp.vTargets[1].SetRandomRatio(0); + + tp.ulThreadNo = 0; + tp.ulRelativeThreadNo = 0; + startingOffset = tp.vTargets[0].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 1000, L"sequential"); + startingOffset = tp.vTargets[1].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 1000, L"sequential"); + + tp.ulThreadNo = 1; + tp.ulRelativeThreadNo = 1; + startingOffset = tp.vTargets[0].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 1000, L"sequential"); + startingOffset = tp.vTargets[1].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 1000, L"sequential"); + + // interlocked seq - both threads, each file at base + tp.vTargets[0].SetUseInterlockedSequential(true); + tp.vTargets[1].SetUseInterlockedSequential(true); + + tp.ulThreadNo = 0; + tp.ulRelativeThreadNo = 0; + startingOffset = tp.vTargets[0].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 1000, L"interlocked sequential"); + startingOffset = tp.vTargets[1].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 1000, L"interlocked sequential"); + + tp.ulThreadNo = 1; + tp.ulRelativeThreadNo = 1; + startingOffset = tp.vTargets[0].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 1000, L"interlocked sequential"); + startingOffset = tp.vTargets[1].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 1000, L"interlocked sequential"); + + tp.vTargets[0].SetUseInterlockedSequential(false); + tp.vTargets[1].SetUseInterlockedSequential(false); + + // parallel async seq - both threads, each file at base + tp.vTargets[0].SetUseParallelAsyncIO(true); + tp.vTargets[1].SetUseParallelAsyncIO(true); + + tp.ulThreadNo = 0; + tp.ulRelativeThreadNo = 0; + startingOffset = tp.vTargets[0].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 1000, L"parasync sequential"); + startingOffset = tp.vTargets[1].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 1000, L"parasync sequential"); + + tp.ulThreadNo = 1; + tp.ulRelativeThreadNo = 1; + startingOffset = tp.vTargets[0].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 1000, L"parasync sequential"); + startingOffset = tp.vTargets[1].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 1000, L"parasync sequential"); + } + + void IORequestGeneratorUnitTests::Test_GetThreadBaseFileOffsetWithStride() + { + Random r; + ThreadParameters tp; + Target target; + + tp.pRand = &r; + target.SetBaseFileOffsetInBytes(1000); + target.SetBlockAlignmentInBytes(500); + target.SetBlockSizeInBytes(1000); + target.SetThreadStrideInBytes(5000); + tp.vTargets.push_back(target); + target.SetThreadStrideInBytes(50000); + tp.vTargets.push_back(target); + + UINT64 startingOffset; + + // normal sequential - first at base, second with stride + tp.vTargets[0].SetRandomRatio(0); + tp.vTargets[1].SetRandomRatio(0); + + tp.ulThreadNo = 0; + tp.ulRelativeThreadNo = 0; + startingOffset = tp.vTargets[0].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 1000, L"sequential"); + startingOffset = tp.vTargets[1].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 1000, L"sequential"); + + tp.ulThreadNo = 1; + tp.ulRelativeThreadNo = 1; + startingOffset = tp.vTargets[0].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 6000, L"sequential"); + startingOffset = tp.vTargets[1].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 51000, L"sequential"); + + // note: unaffected by ulThreadNo + tp.ulRelativeThreadNo = 2; + startingOffset = tp.vTargets[0].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 11000, L"sequential"); + startingOffset = tp.vTargets[1].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 101000, L"sequential"); + + // parallel async seq - first at base, second with stride + tp.vTargets[0].SetUseParallelAsyncIO(true); + tp.vTargets[1].SetUseParallelAsyncIO(true); + + tp.ulThreadNo = 0; + tp.ulRelativeThreadNo = 0; + startingOffset = tp.vTargets[0].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 1000, L"parasync sequential"); + startingOffset = tp.vTargets[1].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 1000, L"parasync sequential"); + + tp.ulThreadNo = 1; + tp.ulRelativeThreadNo = 1; + startingOffset = tp.vTargets[0].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 6000, L"parasync sequential"); + startingOffset = tp.vTargets[1].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 51000, L"parasync sequential"); + + // note: unaffected by ulThreadNo + tp.ulRelativeThreadNo = 2; + startingOffset = tp.vTargets[0].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 11000, L"parasync sequential"); + startingOffset = tp.vTargets[1].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 101000, L"parasync sequential"); + + // interlocked seq - both threads, each file at base + tp.vTargets[0].SetUseInterlockedSequential(true); + tp.vTargets[1].SetUseInterlockedSequential(true); + + // note that thread stride is not permitted with interlocked seq + // this is handled in profile validation + tp.vTargets[0].SetThreadStrideInBytes(0); + tp.vTargets[1].SetThreadStrideInBytes(0); + + tp.ulThreadNo = 0; + tp.ulRelativeThreadNo = 0; + startingOffset = tp.vTargets[0].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 1000, L"interlocked sequential"); + startingOffset = tp.vTargets[1].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 1000, L"interlocked sequential"); + + tp.ulThreadNo = 1; + tp.ulRelativeThreadNo = 1; + startingOffset = tp.vTargets[0].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 1000, L"interlocked sequential"); + startingOffset = tp.vTargets[1].GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); + VERIFY_ARE_EQUAL(startingOffset, 1000, L"interlocked sequential"); + } + + void IORequestGeneratorUnitTests::Test_SequentialWithStrideInterleaved() + { + // this ut handles the case where -T < -s + + Target target; + target.SetThreadStrideInBytes(250); + target.SetBlockAlignmentInBytes(500); + target.SetBlockSizeInBytes(1000); + + Random r; + ThreadParameters tp; + tp.pRand = &r; + tp.vTargets.push_back(target); + + // this is equivalent to -c3000 -T250 -s500 -b1000 + + ThreadTargetState tts(&tp, 0, 3000); + IORequest ior(tp.pRand); + + ULARGE_INTEGER nextOffset; + + // relative thread zero should loop back to base + tp.ulThreadNo = 0; + tp.ulRelativeThreadNo = 0; + { + UINT64 aOff[] = { 0, 500, 1000, 1500, 2000, 0, 500 }; + vector vOff(aOff, aOff + _countof(aOff)); + + tts.NextIORequest(ior); + for (auto off : vOff) + { + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, off, L"case 1"); + tts.NextIORequest(ior); + } + } + + // relative thread one should loop back directly to initial offset + // it will also detect and handle not issuing an IO spanning eof. + tp.ulThreadNo = 1; + tp.ulRelativeThreadNo = 1; + tts.Reset(); + { + UINT64 aOff[] = { 250, 750, 1250, 1750, 250, 750 }; + vector vOff(aOff, aOff + _countof(aOff)); + + tts.NextIORequest(ior); + for (auto off : vOff) + { + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, off, L"case 2"); + tts.NextIORequest(ior); + } + } + + // increasing the stride, relative thread one will loop back to an earlier offset + // before returning to its initial offset + tp.vTargets[0].SetThreadStrideInBytes(750); + tts.Reset(); + { + UINT64 aOff[] = { 750, 1250, 1750, 250, 750, 1250 }; + vector vOff(aOff, aOff + _countof(aOff)); + + tts.NextIORequest(ior); + for (auto off : vOff) + { + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, off, L"case 3"); + tts.NextIORequest(ior); + } + } + } + + void IORequestGeneratorUnitTests::Test_SequentialWithStride() + { + // this ut handles the case where -T > -s + + Target target; + target.SetThreadStrideInBytes(500); + target.SetBlockAlignmentInBytes(250); + target.SetBlockSizeInBytes(1000); + + Random r; + ThreadParameters tp; + tp.pRand = &r; + tp.vTargets.push_back(target); + + // this is equivalent to -c3000 -T500 -s250 -b1000 + + ThreadTargetState tts(&tp, 0, 3000); + IORequest ior(tp.pRand); + + ULARGE_INTEGER nextOffset; + + // relative thread zero should loop back to base + tp.ulThreadNo = 0; + tp.ulRelativeThreadNo = 0; + { + UINT64 aOff[] = { 0, 250, 500, 750, 1000, 1250, 1500, 1750, 2000, 0, 250, 500, 750, 1000, 1250, 1500, 1750, 2000 }; + vector vOff(aOff, aOff + _countof(aOff)); + + tts.NextIORequest(ior); + for (auto off : vOff) + { + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, off, L"case 1"); + tts.NextIORequest(ior); + } + } + + // relative thread one should also loop back to base + tp.ulThreadNo = 1; + tp.ulRelativeThreadNo = 1; + tts.Reset(); + { + UINT64 aOff[] = { 500, 750, 1000, 1250, 1500, 1750, 2000, 0, 250, 500, 750, 1000, 1250, 1500, 1750, 2000 }; + vector vOff(aOff, aOff + _countof(aOff)); + + tts.NextIORequest(ior); + for (auto off : vOff) + { + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, off, L"case 2"); + tts.NextIORequest(ior); + } + } + } + + void IORequestGeneratorUnitTests::Test_SequentialWithStrideUneven() + { + // this ut handles the case where -T > -s and -T is not a multiple of -s + // threads io offsets are disjoint + + Target target; + target.SetThreadStrideInBytes(500); + target.SetBlockAlignmentInBytes(200); + target.SetBlockSizeInBytes(200); + + Random r; + ThreadParameters tp; + tp.pRand = &r; + tp.vTargets.push_back(target); + + // this is equivalent to -c3000 -T500 -s200 -b200 + + ThreadTargetState tts(&tp, 0, 3000); + IORequest ior(tp.pRand); + + ULARGE_INTEGER nextOffset; + + // relative thread zero should loop back to base + tp.ulThreadNo = 0; + tp.ulRelativeThreadNo = 0; + { + UINT64 aOff[] = { 0, 200, 400, 600, 800, 1000, 1200, 1400, 1600, 1800, 2000, 2200, 2400, 2600, 2800, 0, 200, 400 }; + vector vOff(aOff, aOff + _countof(aOff)); + + tts.NextIORequest(ior); + for (auto off : vOff) + { + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, off, L"case 1"); + tts.NextIORequest(ior); + } + } + + // relative thread one should also loop back to base + tp.ulThreadNo = 1; + tp.ulRelativeThreadNo = 1; + tts.Reset(); + { + UINT64 aOff[] = { 500, 700, 900, 1100, 1300, 1500, 1700, 1900, 2100, 2300, 2500, 2700, 100, 300, 500, 700, 900 }; + vector vOff(aOff, aOff + _countof(aOff)); + + tts.NextIORequest(ior); + for (auto off : vOff) + { + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, off, L"case 2"); + tts.NextIORequest(ior); + } + } + } + + void IORequestGeneratorUnitTests::Test_ThreadTargetStateInit() + { + // this ut validates that a constructed ThreadTargetState + // has an initialized IO type, and that it will then agree + // with the first generated IO's type for homegenous/mixed + // write mixes. + + Target target; + target.SetThreadStrideInBytes(500); + target.SetBlockAlignmentInBytes(200); + target.SetBlockSizeInBytes(200); + + Random r; + ThreadParameters tp; + tp.pRand = &r; + tp.vTargets.push_back(target); + + UINT32 writeMix[] = { 0, 50, 100 }; + vector vwriteMix(writeMix, writeMix + _countof(writeMix)); + + for (auto w : vwriteMix) + { + target.SetWriteRatio(w); + + // Validate that ThreadTargetState defines last IO type in all running modes + + VERIFY_ARE_EQUAL(target.GetIOMode(), IOMode::Sequential); + { + ThreadTargetState tts(&tp, 0, 3000); + VERIFY_ARE_NOT_EQUAL(tts._lastIO, IOOperation::Unknown); + IORequest ior(tp.pRand); + VERIFY_ARE_EQUAL(tts._lastIO, ior.GetIoType()); + } + // nothing to undo + + target.SetUseInterlockedSequential(true); + VERIFY_ARE_EQUAL(target.GetIOMode(), IOMode::InterlockedSequential); + { + ThreadTargetState tts(&tp, 0, 3000); + VERIFY_ARE_NOT_EQUAL(tts._lastIO, IOOperation::Unknown); + IORequest ior(tp.pRand); + VERIFY_ARE_EQUAL(tts._lastIO, ior.GetIoType()); + } + target.SetUseInterlockedSequential(false); + + target.SetRandomRatio(50); + VERIFY_ARE_EQUAL(target.GetIOMode(), IOMode::Mixed); + { + ThreadTargetState tts(&tp, 0, 3000); + VERIFY_ARE_NOT_EQUAL(tts._lastIO, IOOperation::Unknown); + IORequest ior(tp.pRand); + VERIFY_ARE_EQUAL(tts._lastIO, ior.GetIoType()); + } + target.SetRandomRatio(0); + + target.SetRandomRatio(100); + VERIFY_ARE_EQUAL(target.GetIOMode(), IOMode::Random); + { + ThreadTargetState tts(&tp, 0, 3000); + VERIFY_ARE_NOT_EQUAL(tts._lastIO, IOOperation::Unknown); + IORequest ior(tp.pRand); + VERIFY_ARE_EQUAL(tts._lastIO, ior.GetIoType()); + } + target.SetRandomRatio(0); + + target.SetUseParallelAsyncIO(true); + VERIFY_ARE_EQUAL(target.GetIOMode(), IOMode::ParallelAsync); + { + ThreadTargetState tts(&tp, 0, 3000); + VERIFY_ARE_NOT_EQUAL(tts._lastIO, IOOperation::Unknown); + IORequest ior(tp.pRand); + VERIFY_ARE_EQUAL(tts._lastIO, ior.GetIoType()); + } + target.SetUseParallelAsyncIO(false); + } + } + + void IORequestGeneratorUnitTests::Test_ThreadTargetStateEffectiveDistPct() + { + // this ut validates that a constructed ThreadTargetState + // has a properly laid out effective distribution given + // the specified percent distribution on the target. + // + // basic cases: + // hole + // degenerate span (covers no offsets) + // rollover to next (degenerate is not last in dist) + // apply to last (degenerate is last in dist) + + Target target; + target.SetBlockAlignmentInBytes(4*KB); + target.SetBlockSizeInBytes(4*KB); + + Random r; + ThreadParameters tp; + tp.pRand = &r; + + vector v; + + // -rdpct10/10:10/10:0/10 + tail + // this is the same distribution in the cmdlineparser UT + v.emplace_back(0, 10, make_pair(0, 10)); + v.emplace_back(10, 10, make_pair(10, 10)); + v.emplace_back(20, 0, make_pair(20, 10)); // zero IO% length hole + v.emplace_back(20, 80, make_pair(30, 70)); + target.SetDistributionRange(v, DistributionType::Percent); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 100); + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 3); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 8*KB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 8*KB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 12*KB); // note length + // note hole removed + VERIFY_ARE_EQUAL(ev[2]._src, (UINT64) 20); /// + VERIFY_ARE_EQUAL(ev[2]._span, (UINT64) 80); + VERIFY_ARE_EQUAL(ev[2]._dst.first, (UINT64) 28*KB); + VERIFY_ARE_EQUAL(ev[2]._dst.second, (UINT64) 72*KB); + tp.vTargets.clear(); + v.clear(); + } + + // + // Degenerate span cases + // + + // -rdpct10/10:10/10:10/1 + tail + // this creates the degenerate - non-degenerate case where + // the non-degenerate must round up. + v.emplace_back(0, 10, make_pair(0, 10)); + v.emplace_back(10, 10, make_pair(10, 10)); + v.emplace_back(20, 10, make_pair(20, 1)); // degenerate < alignment + v.emplace_back(30, 70, make_pair(21, 79)); + target.SetDistributionRange(v, DistributionType::Percent); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 100); + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 4); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 8*KB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 8*KB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 12*KB); + VERIFY_ARE_EQUAL(ev[2]._src, (UINT64) 20); /// degenerate + VERIFY_ARE_EQUAL(ev[2]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[2]._dst.first, (UINT64) 20*KB); + VERIFY_ARE_EQUAL(ev[2]._dst.second, (UINT64) 4*KB); + VERIFY_ARE_EQUAL(ev[3]._src, (UINT64) 30); /// + VERIFY_ARE_EQUAL(ev[3]._span, (UINT64) 70); + VERIFY_ARE_EQUAL(ev[3]._dst.first, (UINT64) 24*KB); + VERIFY_ARE_EQUAL(ev[3]._dst.second, (UINT64) 76*KB); + tp.vTargets.clear(); + v.clear(); + } + + // -rdpct10/96:10/3:80/1 + // tail is degenerate and needs to roll to last + v.emplace_back(0, 10, make_pair(0, 96)); + v.emplace_back(10, 10, make_pair(96, 3)); + v.emplace_back(20, 80, make_pair(99, 1)); // degenerate tail + target.SetDistributionRange(v, DistributionType::Percent); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 100); + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 2); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 96*KB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 90); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 96*KB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 4*KB); + tp.vTargets.clear(); + v.clear(); + } + + // -rdpct10/5:10/1:80/94 + // the degenerate cannot immediately combine with its non-degenerate predecessor, so rolls over + // however, since the predecessor is smaller, it prefers to combine in that direction + v.emplace_back(0, 10, make_pair(0, 5)); + v.emplace_back(10, 10, make_pair(5, 1)); + v.emplace_back(20, 80, make_pair(6, 94)); // non-degenerate tail + target.SetDistributionRange(v, DistributionType::Percent); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 100); + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 2); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 20); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 4*KB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 20); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 80); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 4*KB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 96*KB); + tp.vTargets.clear(); + v.clear(); + } + + // -rdpct10/1:10/1 + // first two are degenerate and combine, tail rounds up and consumes rest + v.emplace_back(0, 10, make_pair(0, 1)); + v.emplace_back(10, 10, make_pair(1, 1)); + v.emplace_back(20, 80, make_pair(2, 98)); // non-degenerate tail + target.SetDistributionRange(v, DistributionType::Percent); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 100); + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 2); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 20); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 4*KB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 20); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 80); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 4*KB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 96*KB); + tp.vTargets.clear(); + v.clear(); + } + + // repeated 10/1 + // This stresses the combination logic + // The first four should successively combine in a [0, 4KiB) range, the fifth + // will land in a new [4KiB,8KiB) range. + // + for (int n = 1; n <= 5; ++n) + { + int m; + for (m = 0; m < n; ++m) + { + v.emplace_back(m*10, 10, make_pair(m*1, 1)); + } + v.emplace_back(m*10, 100 - m*10, make_pair(m, 100 - m)); // non-degenerate tail + target.SetDistributionRange(v, DistributionType::Percent); + tp.vTargets.push_back(target); + + // Fifth case is three ranges; handle seperately. May be worth wrestling down + // how to combine these to allow us to sweep n further, but for now this is fine. + if (n == 5) + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 100); + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 3); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 40); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 4*KB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 40); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 4*KB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 4*KB); + VERIFY_ARE_EQUAL(ev[2]._src, (UINT64) 50); /// + VERIFY_ARE_EQUAL(ev[2]._span, (UINT64) 50); + VERIFY_ARE_EQUAL(ev[2]._dst.first, (UINT64) 8*KB); + VERIFY_ARE_EQUAL(ev[2]._dst.second, (UINT64) 92*KB); + tp.vTargets.clear(); + v.clear(); + break; + } + + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 100); + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 2); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) m*10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 4*KB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) m*10); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 100 - m*10); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 4*KB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 96*KB); + tp.vTargets.clear(); + v.clear(); + } + } + } + + void IORequestGeneratorUnitTests::Test_ThreadTargetStateEffectiveDistAbs() + { + // this ut validates that a constructed ThreadTargetState + // has a properly laid out effective distribution given + // the specified absolute distribution on the target. + + Target target; + target.SetBlockAlignmentInBytes(4*KB); + target.SetBlockSizeInBytes(4*KB); + + Random r; + ThreadParameters tp; + + vector v; + + // -rdabs10/1G:10/1G:0/100G, again producing tail - with autoscale (0) + // this is the same distribution in the cmdlineparser UT + // aligned tail range + v.emplace_back(0,10, make_pair(0, 1*GB)); + v.emplace_back(10,10, make_pair(1*GB, 1*GB)); + v.emplace_back(20,0, make_pair(2*GB, 100*GB)); + v.emplace_back(20,80, make_pair(102*GB, 0)); + target.SetDistributionRange(v, DistributionType::Absolute); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 200*GB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 100); + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 3); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 1*GB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 1*GB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 1*GB); // note length + // note hole removed + VERIFY_ARE_EQUAL(ev[2]._src, (UINT64) 20); /// + VERIFY_ARE_EQUAL(ev[2]._span, (UINT64) 80); + VERIFY_ARE_EQUAL(ev[2]._dst.first, (UINT64) 102*GB); + VERIFY_ARE_EQUAL(ev[2]._dst.second, (UINT64) 98*GB); + tp.vTargets.clear(); + v.clear(); + } + + // -rdabs10/1G:10/1G:0/100G:20/1T + // trim - distribution exceeds target size, end is in last range so no IO% trim + // note same distribution aside from hardening the tail (no autoscale) to a large value + v.emplace_back(0,10, make_pair(0, 1*GB)); + v.emplace_back(10,10, make_pair(1*GB, 1*GB)); + v.emplace_back(20,0, make_pair(2*GB, 100*GB)); + v.emplace_back(20,80, make_pair(102*GB, 1*TB)); + target.SetDistributionRange(v, DistributionType::Absolute); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 200*GB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 100); + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 3); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 1*GB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 1*GB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 1*GB); // note length + // note hole removed + VERIFY_ARE_EQUAL(ev[2]._src, (UINT64) 20); /// + VERIFY_ARE_EQUAL(ev[2]._span, (UINT64) 80); + VERIFY_ARE_EQUAL(ev[2]._dst.first, (UINT64) 102*GB); + VERIFY_ARE_EQUAL(ev[2]._dst.second, (UINT64) 98*GB); + tp.vTargets.clear(); + v.clear(); + } + + // -rdabs10/100G:10/100G + // trim - distribution exceeds target size (autoscale tail); target end aligned to last range start + // drop the IO% of the trailing range + v.emplace_back(0,10, make_pair(0, 100*GB)); + v.emplace_back(10,10, make_pair(100*GB, 100*GB)); + v.emplace_back(20,80, make_pair(200*GB, 0)); + target.SetDistributionRange(v, DistributionType::Absolute); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 200*GB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 2); + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 20); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 100*GB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 100*GB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 100*GB); // note length + tp.vTargets.clear(); + v.clear(); + } + + // -rdabs10/100G:10/200G + // trim - distribution exceeds target size (autoscale tail); last range beyond target end + // drop the IO% of the trailing range + v.emplace_back(0,10, make_pair(0, 100*GB)); + v.emplace_back(10,10, make_pair(100*GB, 200*GB)); + v.emplace_back(20,80, make_pair(300*GB, 0)); + target.SetDistributionRange(v, DistributionType::Absolute); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 200*GB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 2); + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 20); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 100*GB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 100*GB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 100*GB); // note length + tp.vTargets.clear(); + v.clear(); + } + + // + // Unaligned intervals + // + + target.SetBlockAlignmentInBytes(3*KB); + target.SetBlockSizeInBytes(3*KB); + + // -rdabs10/10K:10/10K + // not naturally aligned to target, but is naturally aligned to interval (!) + // 0-10k and 10k-90k - IO all the way to 100K (97K + 3K) is OK + v.emplace_back(0,10, make_pair(0, 10*KB)); + v.emplace_back(10,10, make_pair(10*KB, 90*KB)); + v.emplace_back(20,80, make_pair(300*GB, 0)); + target.SetDistributionRange(v, DistributionType::Absolute); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 2); + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 20); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 10*KB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 10*KB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 90*KB); // note length + tp.vTargets.clear(); + v.clear(); + } + + // -rdabs10/10K:10/1G + // previous distribution, target ends in last range (same result, trimmed) + v.emplace_back(0,10, make_pair(0, 10*KB)); + v.emplace_back(10,10, make_pair(10*KB, 90*KB)); + v.emplace_back(20,80, make_pair(300*GB, 0)); + target.SetDistributionRange(v, DistributionType::Absolute); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 2); + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 20); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 10*KB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 10*KB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 90*KB); // note length + tp.vTargets.clear(); + v.clear(); + } + + // -rdabs10/98k + // autoscale tail cannot issue IO, so distribution is not extended + v.emplace_back(0,10, make_pair(0, 98*KB)); + v.emplace_back(10,90, make_pair(98*KB, 0)); + target.SetDistributionRange(v, DistributionType::Absolute); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 1); + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 10); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 98*KB); + tp.vTargets.clear(); + v.clear(); + } + + // -rdabs10/96k + // autoscale tail can issue single IO @ 96K + v.emplace_back(0,10, make_pair(0, 96*KB)); + v.emplace_back(10,90, make_pair(96*KB, 0)); + target.SetDistributionRange(v, DistributionType::Absolute); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 2); + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 100); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 96*KB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 90); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 96*KB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 4*KB); + tp.vTargets.clear(); + v.clear(); + } + } +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/UnitTests/IORequestGenerator/IORequestGenerator.UnitTests.h b/CristalDiskMark/source/diskspd22/UnitTests/IORequestGenerator/IORequestGenerator.UnitTests.h new file mode 100644 index 0000000..abfe1a3 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/IORequestGenerator/IORequestGenerator.UnitTests.h @@ -0,0 +1,64 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#include "WexTestClass.h" + +namespace UnitTests +{ + BEGIN_MODULE() + MODULE_PROPERTY(L"Feature", L"IORequestGenerator") + END_MODULE() + + class IORequestGeneratorUnitTests : public WEX::TestClass + { + public: + TEST_CLASS(IORequestGeneratorUnitTests); + TEST_METHOD(Test_GetFilesToPrecreate1); + TEST_METHOD(Test_GetFilesToPrecreate2); + TEST_METHOD(Test_GetFilesToPrecreateConstantSizes); + TEST_METHOD(Test_GetFilesToPrecreateConstantOrZeroSizes); + TEST_METHOD(Test_GetFilesToPrecreateUseMaxSize); + + TEST_METHOD(Test_GetNextFileOffsetRandom); + TEST_METHOD(Test_GetNextFileOffsetSequential); + TEST_METHOD(Test_GetNextFileOffsetInterlockedSequential); + TEST_METHOD(Test_GetNextFileOffsetParallelAsyncIO); + TEST_METHOD(Test_SequentialWithStride); + TEST_METHOD(Test_SequentialWithStrideInterleaved); + TEST_METHOD(Test_SequentialWithStrideUneven); + + TEST_METHOD(Test_GetThreadBaseFileOffset); + TEST_METHOD(Test_GetThreadBaseFileOffsetWithStride); + + TEST_METHOD(Test_ThreadTargetStateInit); + TEST_METHOD(Test_ThreadTargetStateEffectiveDistPct); + TEST_METHOD(Test_ThreadTargetStateEffectiveDistAbs); + }; +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/UnitTests/IORequestGenerator/IORequestGenerator.rc b/CristalDiskMark/source/diskspd22/UnitTests/IORequestGenerator/IORequestGenerator.rc new file mode 100644 index 0000000..bcbe27b --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/IORequestGenerator/IORequestGenerator.rc @@ -0,0 +1,17 @@ +#include +#include "Version.h" + +#include + +#define VER_FILETYPE VFT_APP +#define VER_FILESUBTYPE VFT2_UNKNOWN +#define VER_FILEDESCRIPTION_STR "DiskSpd Storage Performance Tool" +#define VER_INTERNALNAME_STR "IORequestGenerator.UnitTests.dll" + +#undef VER_PRODUCTVERSION +#define VER_PRODUCTVERSION DISKSPD_MAJOR,DISKSPD_MINOR,DISKSPD_BUILD,DISKSPD_QFE + +#undef VER_PRODUCTVERSION_STR +#define VER_PRODUCTVERSION_STR DISKSPD_NUMERIC_VERSION_STRING + +#include "common.ver" diff --git a/CristalDiskMark/source/diskspd22/UnitTests/IORequestGenerator/stdafx.h b/CristalDiskMark/source/diskspd22/UnitTests/IORequestGenerator/stdafx.h new file mode 100644 index 0000000..883be10 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/IORequestGenerator/stdafx.h @@ -0,0 +1,33 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + +#include + diff --git a/CristalDiskMark/source/diskspd22/UnitTests/ResultParser/ResultParser.UnitTests.cpp b/CristalDiskMark/source/diskspd22/UnitTests/ResultParser/ResultParser.UnitTests.cpp new file mode 100644 index 0000000..d5e2934 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/ResultParser/ResultParser.UnitTests.cpp @@ -0,0 +1,965 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "StdAfx.h" +#include "ResultParser.UnitTests.h" +#include "Common.h" +#include "resultparser.h" +#include +#include + +using namespace WEX::TestExecution; +using namespace WEX::Logging; +using namespace std; + +namespace UnitTests +{ + void ResultParserUnitTests::Test_ParseResults() + { + Profile profile; + TimeSpan timeSpan; + ResultParser parser; + + Results results; + results.fUseETW = false; + double fTime = 120.0; + results.ullTimeCount = PerfTimer::SecondsToPerfTime(fTime); + + SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION systemProcessorInfo = {}; + systemProcessorInfo.UserTime.QuadPart = static_cast(fTime * 30 * 100000); + systemProcessorInfo.IdleTime.QuadPart = static_cast(fTime * 45 * 100000); + systemProcessorInfo.KernelTime.QuadPart = static_cast(fTime * 70 * 100000); + results.vSystemProcessorPerfInfo.push_back(systemProcessorInfo); + + TargetResults targetResults; + targetResults.sPath = "testfile1.dat"; + targetResults.ullFileSize = 10 * 1024 * 1024; + targetResults.ullReadBytesCount = 4 * 1024 * 1024; + targetResults.ullReadIOCount = 6; + targetResults.ullWriteBytesCount = 2 * 1024 * 1024; + targetResults.ullWriteIOCount = 10; + targetResults.ullBytesCount = targetResults.ullReadBytesCount + targetResults.ullWriteBytesCount; + targetResults.ullIOCount = targetResults.ullReadIOCount + targetResults.ullWriteIOCount; + + // TODO: Histogram readLatencyHistogram; + // TODO: Histogram writeLatencyHistogram; + + // TODO: IoBucketizer writeBucketizer; + + timeSpan.SetCalculateIopsStdDev(true); + targetResults.readBucketizer.Initialize(1000, timeSpan.GetDuration()); + for (size_t i = 0; i < timeSpan.GetDuration(); i++) + { + // add an io halfway through the bucket's time interval + targetResults.readBucketizer.Add(i * 1000 + 500, 100); + } + + ThreadResults threadResults; + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + + vector vResults; + vResults.push_back(results); + + // just throw away the computername - for the ut, it's as useful (and simpler) + // to verify a static null as anything else. + SystemInformation system; + system.sComputerName.clear(); + system.ResetTime(); + + // and power plan + system.sActivePolicyName.clear(); + system.sActivePolicyGuid.clear(); + + system.processorTopology._ulProcessorCount = 1; + system.processorTopology._ubPerformanceEfficiencyClass = 0; + system.processorTopology._fSMT = false; + + system.processorTopology._vProcessorGroupInformation.clear(); + system.processorTopology._vProcessorGroupInformation.emplace_back((WORD)0, (BYTE)1, (BYTE)1, (KAFFINITY)0x1); + + ProcessorNumaInformation node; + node._nodeNumber = 0; + node._vProcessorMasks.emplace_back((WORD)0, (KAFFINITY)0x1); + system.processorTopology._vProcessorNumaInformation.clear(); + system.processorTopology._vProcessorNumaInformation.push_back(node); + + ProcessorSocketInformation socket; + socket._vProcessorMasks.emplace_back((WORD)0, (KAFFINITY)0x1); + system.processorTopology._vProcessorSocketInformation.clear(); + system.processorTopology._vProcessorSocketInformation.push_back(socket); + + system.processorTopology._vProcessorCoreInformation.clear(); + system.processorTopology._vProcessorCoreInformation.emplace_back((WORD)0, (KAFFINITY)0x1, (BYTE)0); + + // finally, add the timespan to the profile and dump. + profile.AddTimeSpan(timeSpan); + + string sResults = parser.ParseResults(profile, system, vResults); + + // stringify random text, quoting "'s and adding newline/preserving tabs + // gc some.txt |% { write-host $("`"{0}\n`"" -f $($_ -replace "`"","\`"" -replace "`t","\t")) } + + const char *pcszExpectedOutput = "\n" + "Command Line: \n" + "\n" + "Input parameters:\n" + "\n" + "\ttimespan: 1\n" + "\t-------------\n" + "\tduration: 10s\n" + "\twarm up time: 5s\n" + "\tcool down time: 0s\n" + "\tgathering IOPS at intervals of 1000ms\n" + "\trandom seed: 0\n" + "\n" + "System information:\n\n" + "\tcomputer name: \n" + "\tstart time: \n" + "\n" + "\tcpu count:\t\t1\n" + "\tcore count:\t\t1\n" + "\tgroup count:\t\t1\n" + "\tnode count:\t\t1\n" + "\tsocket count:\t\t1\n" + "\theterogeneous cores:\tn\n" + "\n" + "\tactive power scheme:\t\n" + "\n" + "Results for timespan 1:\n" + "*******************************************************************************\n" + "\n" + "actual test time:\t120.00s\n" + "thread count:\t\t1\n" + "\n" + "CPU | Usage | User | Kernel | Idle\n" + "----------------------------------------\n" + " 0| 55.00%| 30.00%| 25.00%| 45.00%\n" + "----------------------------------------\n" + "avg.| 55.00%| 30.00%| 25.00%| 45.00%\n" + "\n" + "Total IO\n" + "thread | bytes | I/Os | MiB/s | I/O per s | IopsStdDev | file\n" + "-------------------------------------------------------------------------------------------\n" + " 0 | 6291456 | 16 | 0.05 | 0.13 | 0.00 | testfile1.dat (10MiB)\n" + "-------------------------------------------------------------------------------------------\n" + "total: 6291456 | 16 | 0.05 | 0.13 | 0.00\n" + "\n" + "Read IO\n" + "thread | bytes | I/Os | MiB/s | I/O per s | IopsStdDev | file\n" + "-------------------------------------------------------------------------------------------\n" + " 0 | 4194304 | 6 | 0.03 | 0.05 | 0.00 | testfile1.dat (10MiB)\n" + "-------------------------------------------------------------------------------------------\n" + "total: 4194304 | 6 | 0.03 | 0.05 | 0.00\n" + "\n" + "Write IO\n" + "thread | bytes | I/Os | MiB/s | I/O per s | IopsStdDev | file\n" + "-------------------------------------------------------------------------------------------\n" + " 0 | 2097152 | 10 | 0.02 | 0.08 | 0.00 | testfile1.dat (10MiB)\n" + "-------------------------------------------------------------------------------------------\n" + "total: 2097152 | 10 | 0.02 | 0.08 | 0.00\n"; + VERIFY_ARE_EQUAL(sResults, pcszExpectedOutput); + } + + void ResultParserUnitTests::Test_ParseProfile() + { + Profile profile; + ResultParser parser; + TimeSpan timeSpan; + Target target; + + timeSpan.AddTarget(target); + profile.AddTimeSpan(timeSpan); + + string s = parser.ParseProfile(profile); + const char *pszExpectedResult = "\nCommand Line: \n" + "\n" + "Input parameters:\n" + "\n" + "\ttimespan: 1\n" + "\t-------------\n" + "\tduration: 10s\n" + "\twarm up time: 5s\n" + "\tcool down time: 0s\n" + "\trandom seed: 0\n" + "\tpath: ''\n" + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tthreads per file: 1\n" + "\t\tusing I/O Completion Ports\n" + "\t\tIO priority: normal\n\n"; + + VERIFY_ARE_EQUAL(strlen(pszExpectedResult), s.length()); + VERIFY_IS_TRUE(!strcmp(pszExpectedResult, s.c_str())); + } + + void ResultParserUnitTests::Test_PrintTarget() + { + ResultParser parser; + Target target; + + parser._PrintTarget(target, false, true, false); + const char *pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + + parser._sResult.clear(); + target.SetThreadStrideInBytes(100 * 1024); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tthread stride size: 100KiB\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + target.SetThreadStrideInBytes(0); + + parser._sResult.clear(); + target.SetMaxFileSize(2000 * 1024); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tmax file size: 1.95MiB\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + target.SetMaxFileSize(0); + + parser._sResult.clear(); + target.SetBaseFileOffsetInBytes(2 * 1024 * 1024); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tbase file offset: 2MiB\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + target.SetBaseFileOffsetInBytes(0); + + parser._sResult.clear(); + target.SetThroughput(1000); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tIO priority: normal\n" + "\t\tthroughput rate-limited to 1000 B/ms\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + target.SetThroughput(0); + + parser._sResult.clear(); + target.SetThroughputIOPS(1000); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tIO priority: normal\n" + "\t\tthroughput rate-limited to 1000 IOPS\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + target.SetThroughputIOPS(0); + + parser._sResult.clear(); + target.SetWriteRatio(30); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming mix test (read/write ratio: 70/30)\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + target.SetWriteRatio(0); + + parser._sResult.clear(); + target.SetRandomDataWriteBufferSize(12341234); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\twrite buffer size: 11.77MiB\n" + "\t\twrite buffer source: random fill\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + + parser._sResult.clear(); + target.SetRandomDataWriteBufferSourcePath("x:\\foo\\bar.dat"); + target.SetRandomRatio(100); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\twrite buffer size: 11.77MiB\n" + "\t\twrite buffer source: 'x:\\foo\\bar.dat'\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + target.SetRandomDataWriteBufferSize(0); + target.SetRandomDataWriteBufferSourcePath(""); + + parser._sResult.clear(); + target.SetCacheMode(TargetCacheMode::DisableOSCache); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tsoftware cache disabled\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + + parser._sResult.clear(); + target.SetCacheMode(TargetCacheMode::DisableOSCache); + target.SetWriteThroughMode(WriteThroughMode::On); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tsoftware cache disabled\n" + "\t\thardware write cache disabled, writethrough on\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + + parser._sResult.clear(); + target.SetCacheMode(TargetCacheMode::Cached); + target.SetWriteThroughMode(WriteThroughMode::On); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\thardware and software write caches disabled, writethrough on\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + target.SetWriteThroughMode(WriteThroughMode::Undefined); + + parser._sResult.clear(); + target.SetCacheMode(TargetCacheMode::DisableLocalCache); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tlocal software cache disabled, remote cache enabled\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + + parser._sResult.clear(); + target.SetCacheMode(TargetCacheMode::Cached); + target.SetMemoryMappedIoMode(MemoryMappedIoMode::On); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tmemory mapped I/O enabled\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + + parser._sResult.clear(); + target.SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::ViewOfFile); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tmemory mapped I/O enabled, flush mode: FlushViewOfFile\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + + parser._sResult.clear(); + target.SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::NonVolatileMemory); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tmemory mapped I/O enabled, flush mode: FlushNonVolatileMemory\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + + parser._sResult.clear(); + target.SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tmemory mapped I/O enabled, flush mode: FlushNonVolatileMemory with no drain\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + + parser._sResult.clear(); + target.SetMemoryMappedIoMode(MemoryMappedIoMode::Off); + target.SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::Undefined); + target.SetCacheMode(TargetCacheMode::DisableLocalCache); + target.SetTemporaryFileHint(true); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tlocal software cache disabled, remote cache enabled\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tusing FILE_ATTRIBUTE_TEMPORARY hint\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + + parser._sResult.clear(); + target.SetRandomAccessHint(true); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tlocal software cache disabled, remote cache enabled\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tusing FILE_FLAG_RANDOM_ACCESS hint\n" + "\t\tusing FILE_ATTRIBUTE_TEMPORARY hint\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + + parser._sResult.clear(); + target.SetRandomAccessHint(false); + target.SetTemporaryFileHint(false); + target.SetSequentialScanHint(true); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tlocal software cache disabled, remote cache enabled\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tusing FILE_FLAG_SEQUENTIAL_SCAN hint\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + } + + void ResultParserUnitTests::Test_PrintTargetDistributionPct() + { + ResultParser parser; + Target target; + + vector v; + + // these match the CmdLineParser UTs + + // -rdpct10/10:10/10:0/10, though we need to produce the tail here + v.emplace_back(0, 10, make_pair(0, 10)); + v.emplace_back(10, 10, make_pair(10, 10)); + v.emplace_back(20, 0, make_pair(20, 10)); // zero IO% length hole + v.emplace_back(20, 80, make_pair(30, 70)); + target.SetDistributionRange(v, DistributionType::Percent); + + parser._PrintTarget(target, false, true, false); + const char *pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tIO priority: normal\n" + "\t\tIO Distribution:\n" + "\t\t 10% of IO => [ 0% - 10%) of target\n" + "\t\t 10% of IO => [10% - 20%) of target\n" + "\t\t 0% of IO => [20% - 30%) of target\n" + "\t\t 80% of IO => [30% - 100%) of target\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + v.clear(); + parser._sResult.clear(); + } + + void ResultParserUnitTests::Test_PrintTargetDistributionAbs() + { + ResultParser parser; + Target target; + + vector v; + + // these match the CmdLineParser UTs + + // -rdabs10/1G:10/1G:0/100G, again producing tail + v.emplace_back(0,10, make_pair(0, 1*GB)); + v.emplace_back(10,10, make_pair(1*GB, 1*GB)); + v.emplace_back(20,0, make_pair(2*GB, 100*GB)); + v.emplace_back(20,80, make_pair(102*GB, 0)); + target.SetDistributionRange(v, DistributionType::Absolute); + + parser._PrintTarget(target, false, true, false); + const char *pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tIO priority: normal\n" + "\t\tIO Distribution:\n" + "\t\t 10% of IO => [ 0 - 1GiB)\n" + "\t\t 10% of IO => [ 1GiB - 2GiB)\n" + "\t\t 0% of IO => [ 2GiB - 102GiB)\n" + "\t\t 80% of IO => [ 102GiB - end)\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + v.clear(); + parser._sResult.clear(); + } + + void ResultParserUnitTests::Test_PrintEffectiveDistributionPct() + { + // the first matches the corresponding IORequestGenerator effdist UT + ResultParser parser; + + Target target; + target.SetBlockAlignmentInBytes(4*KB); + target.SetBlockSizeInBytes(4*KB); + + Random r; + ThreadParameters tp; + tp.pRand = &r; + + vector v; + + // -rdpct10/10:10/10:0/10 + tail + // this is the same distribution in the cmdlineparser UT + v.emplace_back(0, 10, make_pair(0, 10)); + v.emplace_back(10, 10, make_pair(10, 10)); + v.emplace_back(20, 0, make_pair(20, 10)); // zero IO% length hole + v.emplace_back(20, 80, make_pair(30, 70)); + target.SetDistributionRange(v, DistributionType::Percent); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + + Results results; + ThreadResults threadResults; + TargetResults targetResults; + + targetResults.sPath = "testfile.dat"; + targetResults.vDistributionRange = tts._vDistributionRange; + + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + + parser._PrintEffectiveDistributions(results); + + const char* pcszResults = "\nEffective IO Distributions\n" \ + "--------------------------\n" + "target: testfile.dat [thread: 0]\n" + " 10% of IO => [ 0 - 8KiB)\n" + " 10% of IO => [ 8KiB - 20KiB)\n" + " 80% of IO => [ 28KiB - 100KiB)\n"; + VERIFY_ARE_EQUAL(pcszResults, parser._sResult); + + parser._sResult.clear(); + } + + // + // Tests of distribution deduplication. + // + + // now repeat, duplicating the thread result for a second thread + + { + ThreadTargetState tts(&tp, 0, 100*KB); + + Results results; + ThreadResults threadResults; + TargetResults targetResults; + + targetResults.sPath = "testfile.dat"; + targetResults.vDistributionRange = tts._vDistributionRange; + + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + results.vThreadResults.push_back(threadResults); + + parser._PrintEffectiveDistributions(results); + + const char* pcszResults = "\nEffective IO Distributions\n" \ + "--------------------------\n" + "target: testfile.dat [thread: 0 1]\n" + " 10% of IO => [ 0 - 8KiB)\n" + " 10% of IO => [ 8KiB - 20KiB)\n" + " 80% of IO => [ 28KiB - 100KiB)\n"; + VERIFY_ARE_EQUAL(pcszResults, parser._sResult); + + parser._sResult.clear(); + } + + // now repeat, for a third thread - ellision + + { + ThreadTargetState tts(&tp, 0, 100*KB); + + Results results; + ThreadResults threadResults; + TargetResults targetResults; + + targetResults.sPath = "testfile.dat"; + targetResults.vDistributionRange = tts._vDistributionRange; + + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + results.vThreadResults.push_back(threadResults); + results.vThreadResults.push_back(threadResults); + + parser._PrintEffectiveDistributions(results); + + const char* pcszResults = "\nEffective IO Distributions\n" \ + "--------------------------\n" + "target: testfile.dat [thread: 0 - 2]\n" + " 10% of IO => [ 0 - 8KiB)\n" + " 10% of IO => [ 8KiB - 20KiB)\n" + " 80% of IO => [ 28KiB - 100KiB)\n"; + VERIFY_ARE_EQUAL(pcszResults, parser._sResult); + + parser._sResult.clear(); + } + + // now repeat, moving the third thread to a different target + + { + ThreadTargetState tts(&tp, 0, 100*KB); + + Results results; + ThreadResults threadResults; + TargetResults targetResults; + + targetResults.vDistributionRange = tts._vDistributionRange; + + targetResults.sPath = "testfile.dat"; + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + results.vThreadResults.push_back(threadResults); + + targetResults.sPath = "testfile2.dat"; + threadResults.vTargetResults.clear(); + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + + parser._PrintEffectiveDistributions(results); + + const char* pcszResults = "\nEffective IO Distributions\n" \ + "--------------------------\n" + "target: testfile.dat [thread: 0 1]\n" + "target: testfile2.dat [thread: 2]\n" + " 10% of IO => [ 0 - 8KiB)\n" + " 10% of IO => [ 8KiB - 20KiB)\n" + " 80% of IO => [ 28KiB - 100KiB)\n"; + VERIFY_ARE_EQUAL(pcszResults, parser._sResult); + + parser._sResult.clear(); + } + + // now repeat, four threads on the first target, three contiguous and one not + // the thread on the second target is used to create the gap - ellision will occur + // and the fourth thread will stand alone + + { + ThreadTargetState tts(&tp, 0, 100*KB); + + Results results; + ThreadResults threadResults; + TargetResults targetResults; + + targetResults.vDistributionRange = tts._vDistributionRange; + + targetResults.sPath = "testfile.dat"; + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + results.vThreadResults.push_back(threadResults); + results.vThreadResults.push_back(threadResults); + + threadResults.vTargetResults.clear(); + targetResults.sPath = "testfile2.dat"; + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + + threadResults.vTargetResults.clear(); + targetResults.sPath = "testfile.dat"; + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + + parser._PrintEffectiveDistributions(results); + + const char* pcszResults = "\nEffective IO Distributions\n" \ + "--------------------------\n" + "target: testfile.dat [thread: 0 - 2 4]\n" + "target: testfile2.dat [thread: 3]\n" + " 10% of IO => [ 0 - 8KiB)\n" + " 10% of IO => [ 8KiB - 20KiB)\n" + " 80% of IO => [ 28KiB - 100KiB)\n"; + VERIFY_ARE_EQUAL(pcszResults, parser._sResult); + + parser._sResult.clear(); + } + + // two distinct distributions which share the same IO% + + { + ThreadTargetState tts1(&tp, 0, 100*KB); + ThreadTargetState tts2(&tp, 0, 1*MB); + + Results results; + ThreadResults threadResults; + TargetResults targetResults; + + targetResults.vDistributionRange = tts1._vDistributionRange; + targetResults.sPath = "testfile.dat"; + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + + threadResults.vTargetResults.clear(); + + targetResults.vDistributionRange = tts2._vDistributionRange; + targetResults.sPath = "testfile2.dat"; + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + + parser._PrintEffectiveDistributions(results); + + const char* pcszResults = "\nEffective IO Distributions\n" \ + "--------------------------\n" + "target: testfile.dat [thread: 0]\n" + " 10% of IO => [ 0 - 8KiB)\n" + " 10% of IO => [ 8KiB - 20KiB)\n" + " 80% of IO => [ 28KiB - 100KiB)\n" + "target: testfile2.dat [thread: 1]\n" + " 10% of IO => [ 0 - 100KiB)\n" + " 10% of IO => [ 100KiB - 204KiB)\n" + " 80% of IO => [ 304KiB - 1MiB)\n"; + VERIFY_ARE_EQUAL(pcszResults, parser._sResult); + + parser._sResult.clear(); + } + + tp.vTargets.clear(); + v.clear(); + } + + void ResultParserUnitTests::Test_PrintEffectiveDistributionAbs() + { + // the first matches the corresponding IORequestGenerator effdist UT + ResultParser parser; + + Target target; + target.SetBlockAlignmentInBytes(4*KB); + target.SetBlockSizeInBytes(4*KB); + + Random r; + ThreadParameters tp; + + vector v; + + // -rdabs10/1G:10/1G:0/100G, again producing tail - with autoscale (0) + // this is the same distribution in the cmdlineparser UT + // aligned tail range + v.emplace_back(0,10, make_pair(0, 1*GB)); + v.emplace_back(10,10, make_pair(1*GB, 1*GB)); + v.emplace_back(20,0, make_pair(2*GB, 100*GB)); + v.emplace_back(20,80, make_pair(102*GB, 0)); + target.SetDistributionRange(v, DistributionType::Absolute); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 200*GB); + + Results results; + ThreadResults threadResults; + TargetResults targetResults; + + targetResults.sPath = "testfile.dat"; + targetResults.vDistributionRange = tts._vDistributionRange; + + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + + parser._PrintEffectiveDistributions(results); + + const char* pcszResults = "\nEffective IO Distributions\n" \ + "--------------------------\n" + "target: testfile.dat [thread: 0]\n" + " 10% of IO => [ 0 - 1GiB)\n" + " 10% of IO => [ 1GiB - 2GiB)\n" + " 80% of IO => [ 102GiB - 200GiB)\n"; + VERIFY_ARE_EQUAL(pcszResults, parser._sResult); + + tp.vTargets.clear(); + v.clear(); + parser._sResult.clear(); + } + + // -rdabs10/50k:20/10k:30/1G on 100KiB target - autoscale tail, but trimmed on last spec'd range + // this results in logical truncation since the covered ranges are only 60% of IO%, a case which + // is specific to absolute distributions. + v.emplace_back(0, 10, make_pair(0, 50*KB)); + v.emplace_back(10, 20, make_pair(50*KB, 10*KB)); + v.emplace_back(30, 30, make_pair(60*KB, 1*GB)); + v.emplace_back(60, 40, make_pair(1*GB + 60*KB, 0)); + target.SetDistributionRange(v, DistributionType::Absolute); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + + Results results; + ThreadResults threadResults; + TargetResults targetResults; + + targetResults.sPath = "testfile.dat"; + targetResults.vDistributionRange = tts._vDistributionRange; + + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + + parser._PrintEffectiveDistributions(results); + + const char* pcszResults = "\nEffective IO Distributions\n" \ + "--------------------------\n" + "target: testfile.dat [thread: 0]\n" + " 16.7% of IO => [ 0 - 50KiB)\n" + " 33.3% of IO => [ 50KiB - 60KiB)\n" + " 50.0% of IO => [ 60KiB - 100KiB)\n"; + VERIFY_ARE_EQUAL(pcszResults, parser._sResult); + + tp.vTargets.clear(); + v.clear(); + parser._sResult.clear(); + } + } +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/UnitTests/ResultParser/ResultParser.UnitTests.h b/CristalDiskMark/source/diskspd22/UnitTests/ResultParser/ResultParser.UnitTests.h new file mode 100644 index 0000000..d909d56 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/ResultParser/ResultParser.UnitTests.h @@ -0,0 +1,52 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#include "WexTestClass.h" + +namespace UnitTests +{ + BEGIN_MODULE() + MODULE_PROPERTY(L"Feature", L"ResultParser") + END_MODULE() + + class ResultParserUnitTests : public WEX::TestClass + { + public: + TEST_CLASS(ResultParserUnitTests); + TEST_METHOD(Test_ParseResults); + TEST_METHOD(Test_ParseProfile); + TEST_METHOD(Test_PrintTarget); + TEST_METHOD(Test_PrintTargetDistributionPct); + TEST_METHOD(Test_PrintTargetDistributionAbs); + TEST_METHOD(Test_PrintEffectiveDistributionPct); + TEST_METHOD(Test_PrintEffectiveDistributionAbs); + }; +} + diff --git a/CristalDiskMark/source/diskspd22/UnitTests/ResultParser/ResultParser.rc b/CristalDiskMark/source/diskspd22/UnitTests/ResultParser/ResultParser.rc new file mode 100644 index 0000000..63e9e63 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/ResultParser/ResultParser.rc @@ -0,0 +1,17 @@ +#include +#include "Version.h" + +#include + +#define VER_FILETYPE VFT_APP +#define VER_FILESUBTYPE VFT2_UNKNOWN +#define VER_FILEDESCRIPTION_STR "DiskSpd Storage Performance Tool" +#define VER_INTERNALNAME_STR "ResultParser.UnitTests.dll" + +#undef VER_PRODUCTVERSION +#define VER_PRODUCTVERSION DISKSPD_MAJOR,DISKSPD_MINOR,DISKSPD_BUILD,DISKSPD_QFE + +#undef VER_PRODUCTVERSION_STR +#define VER_PRODUCTVERSION_STR DISKSPD_NUMERIC_VERSION_STRING + +#include "common.ver" diff --git a/CristalDiskMark/source/diskspd22/UnitTests/ResultParser/stdafx.h b/CristalDiskMark/source/diskspd22/UnitTests/ResultParser/stdafx.h new file mode 100644 index 0000000..883be10 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/ResultParser/stdafx.h @@ -0,0 +1,33 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + +#include + diff --git a/CristalDiskMark/source/diskspd22/UnitTests/XmlProfileParser/XmlProfileParser.UnitTests.cpp b/CristalDiskMark/source/diskspd22/UnitTests/XmlProfileParser/XmlProfileParser.UnitTests.cpp new file mode 100644 index 0000000..116de8d --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/XmlProfileParser/XmlProfileParser.UnitTests.cpp @@ -0,0 +1,1672 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "StdAfx.h" +#include "XmlProfileParser.UnitTests.h" +#include + +using namespace WEX::TestExecution; +using namespace WEX::Logging; +using namespace std; + +namespace UnitTests +{ + bool ModuleSetup() + { + return true; + } + + bool ModuleCleanup() + { + return true; + } + + bool XmlProfileParserUnitTests::ClassSetup() + { + bool fOk = true; + _hModule = GetModuleHandle(L"XmlProfileParser.UnitTests.dll"); + char szTempDirPath[MAX_PATH] = {}; + DWORD cch = GetTempPathA(_countof(szTempDirPath), szTempDirPath); + if (cch != 0) + { + _sTempFilePath = szTempDirPath; + _sTempFilePath += "DiskSpdXmlProfileParser.xml"; + //printf("deleting %s\n", _sTempFilePath.c_str()); + DeleteFileA(_sTempFilePath.c_str()); + } + else + { + fOk = false; + } + return fOk; + } + + bool XmlProfileParserUnitTests::ClassCleanup() + { + return true; + } + + bool XmlProfileParserUnitTests::MethodSetup() + { + return true; + } + + bool XmlProfileParserUnitTests::MethodCleanup() + { + DeleteFileA(_sTempFilePath.c_str()); + return true; + } + + void XmlProfileParserUnitTests::Test_ParseFile() + { + FILE *pFile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, "\n" + "\n" + " true\n" + " 15\n" + " \n" + " \n" + " 10\n" + " 20\n" + " 30\n" + " 40\n" + " 50\n" + " true\n" + " true\n" + " true\n" + " \n" + " \n" + " testfile.dat\n" + " 100\n" + " 123\n" + " 234\n" + " 333\n" + " true\n" + " true\n" + " true\n" + " 3344\n" + " 4433\n" + " 56\n" + " 561\n" + " 84\n" + " 86\n" + " 88\n" + " true\n" + " true\n" + " true\n" + " 60\n" + " \n" + " \n" + " testfile2.dat\n" + " 200\n" + " 85\n" + " 2\n" + " 2222\n" + " true\n" + " \n" + " \n" + " testfile3.dat\n" + " 200\n" + " 85\n" + " 2\n" + " 2222\n" + " true\n" + " true\n" + " true\n" + " \n" + " \n" + " testfile4.dat\n" + " 200\n" + " 85\n" + " 2\n" + " 2222\n" + " true\n" + " true\n" + " true\n" + " \n" + " \n" + " \n" + " \n" + " 10\n" + " 20\n" + " 30\n" + " 40\n" + " 50\n" + " true\n" + " true\n" + " \n" + " \n" + " testfile.dat\n" + " 100\n" + " 84\n" + " true\n" + " \n" + " \n" + " testfile1.dat\n" + " 100\n" + " 84\n" + " true\n" + " ViewOfFile" + " \n" + " \n" + " testfile2.dat\n" + " 100\n" + " 84\n" + " true\n" + " NonVolatileMemory" + " \n" + " \n" + " testfile3.dat\n" + " 100\n" + " 84\n" + " true\n" + " NonVolatileMemoryNoDrain" + " \n" + " \n" + " \n" + " \n" + "\n"); +/* + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false);*/ + fclose(pFile); + + XmlProfileParser p; + Profile profile; + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + // note: many conflicts, this ut is a broad blend of specific element parsing, NOT a valid profile + VERIFY_IS_FALSE(profile.Validate(false)); + VERIFY_IS_TRUE(profile.GetVerbose() == true); + VERIFY_IS_TRUE(profile.GetProgress() == 15); +// TODO: VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b4K -w84 -a1,4,6 testfile.dat testfile2.dat") == 0); + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); + VERIFY_IS_TRUE(profile.GetEtwProcess() == false); + VERIFY_IS_TRUE(profile.GetEtwThread() == false); + VERIFY_IS_TRUE(profile.GetEtwImageLoad() == false); + VERIFY_IS_TRUE(profile.GetEtwDiskIO() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryPageFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwMemoryHardFaults() == false); + VERIFY_IS_TRUE(profile.GetEtwNetwork() == false); + VERIFY_IS_TRUE(profile.GetEtwRegistry() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePagedMemory() == false); + VERIFY_IS_TRUE(profile.GetEtwUsePerfTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseSystemTimer() == false); + VERIFY_IS_TRUE(profile.GetEtwUseCyclesCounter() == false); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)2); + VERIFY_ARE_EQUAL(vSpans[0].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[0].GetWarmup(), (UINT32)20); + VERIFY_ARE_EQUAL(vSpans[0].GetCooldown(), (UINT32)30); + VERIFY_ARE_EQUAL(vSpans[0].GetRandSeed(), (UINT32)40); + VERIFY_ARE_EQUAL(vSpans[0].GetThreadCount(), (DWORD)50); + VERIFY_ARE_EQUAL(vSpans[0].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[0].GetDisableAffinity() == true); + VERIFY_IS_TRUE(vSpans[0].GetCompletionRoutines() == true); + VERIFY_IS_TRUE(vSpans[0].GetMeasureLatency() == true); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)4); + Target t(vTargets[0]); + + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(100)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)123); + VERIFY_IS_TRUE(t.GetRandomRatio() == 100); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), 234); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 333); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == true); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::DisableOSCache); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::On); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)3344); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 4433); + VERIFY_IS_TRUE(t.GetCreateFile() == true); + VERIFY_ARE_EQUAL(t.GetFileSize(), 56); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 561); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetUseBurstSize() == true); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)86); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)88); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == true); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == true); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == true); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == true); + VERIFY_IS_TRUE(t.GetIOPriorityHint() == IoPriorityHintNormal); + VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)60); + + t = vTargets[1]; + VERIFY_IS_TRUE(t.GetPath().compare("testfile2.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(200)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == true); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), 2222); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)85); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == false); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_IS_TRUE(t.GetIOPriorityHint() == IoPriorityHintLow); + VERIFY_IS_TRUE(profile.GetPrecreateFiles() == PrecreateFiles::None); + + // verify DisableLocalCache + t = vTargets[2]; + VERIFY_IS_TRUE(t.GetPath().compare("testfile3.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(200)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == true); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), 2222); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::DisableLocalCache); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)85); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == true); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_IS_TRUE(t.GetIOPriorityHint() == IoPriorityHintLow); + VERIFY_IS_TRUE(profile.GetPrecreateFiles() == PrecreateFiles::None); + + // verify DisableAllCache + t = vTargets[3]; + VERIFY_IS_TRUE(t.GetPath().compare("testfile4.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(200)); + VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); + VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == true); + VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), 2222); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); + VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); + VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::DisableOSCache); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::Off); + VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::On); + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)1); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), 0); + VERIFY_IS_TRUE(t.GetCreateFile() == false); + VERIFY_ARE_EQUAL(t.GetFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)85); + VERIFY_IS_TRUE(t.GetUseBurstSize() == false); + VERIFY_ARE_EQUAL(t.GetBurstSize(), (DWORD)0); + VERIFY_ARE_EQUAL(t.GetThinkTime(), (DWORD)0); + VERIFY_IS_TRUE(t.GetEnableThinkTime() == false); + VERIFY_IS_TRUE(t.GetSequentialScanHint() == false); + VERIFY_IS_TRUE(t.GetRandomAccessHint() == false); + VERIFY_IS_TRUE(t.GetTemporaryFileHint() == true); + VERIFY_IS_TRUE(t.GetUseLargePages() == false); + VERIFY_IS_TRUE(t.GetIOPriorityHint() == IoPriorityHintLow); + VERIFY_IS_TRUE(profile.GetPrecreateFiles() == PrecreateFiles::None); + + VERIFY_ARE_EQUAL(vSpans[1].GetDuration(), (UINT32)10); + VERIFY_ARE_EQUAL(vSpans[1].GetWarmup(), (UINT32)20); + VERIFY_ARE_EQUAL(vSpans[1].GetCooldown(), (UINT32)30); + VERIFY_ARE_EQUAL(vSpans[1].GetRandSeed(), (UINT32)40); + VERIFY_ARE_EQUAL(vSpans[1].GetThreadCount(), (DWORD)50); + VERIFY_ARE_EQUAL(vSpans[1].GetRequestCount(), (DWORD)0); + VERIFY_IS_TRUE(vSpans[1].GetDisableAffinity() == true); + VERIFY_IS_TRUE(vSpans[1].GetMeasureLatency() == true); + + vTargets = vSpans[1].GetTargets(); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)4); + + t = vTargets[0]; + VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(100)); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::On); + + // verify FlushViewOfFile + t = vTargets[1]; + VERIFY_IS_TRUE(t.GetPath().compare("testfile1.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(100)); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::On); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::ViewOfFile); + + // verify RtlFlushNonVolatileMemory + t = vTargets[2]; + VERIFY_IS_TRUE(t.GetPath().compare("testfile2.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(100)); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::On); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::NonVolatileMemory); + + // verify RtlFlushNonVolatileMemoryNoDrain + t = vTargets[3]; + VERIFY_IS_TRUE(t.GetPath().compare("testfile3.dat") == 0); + VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(100)); + VERIFY_ARE_EQUAL(t.GetWriteRatio(), (DWORD)84); + VERIFY_IS_TRUE(t.GetMemoryMappedIoMode() == MemoryMappedIoMode::On); + VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain); + } + + void XmlProfileParserUnitTests::Test_ParseFilePrecreateFilesUseMaxSize() + { + FILE *pFile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " UseMaxSize\n" + "\n"); + fclose(pFile); + + XmlProfileParser p; + Profile profile; + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_IS_TRUE(profile.GetPrecreateFiles() == PrecreateFiles::UseMaxSize); + } + + void XmlProfileParserUnitTests::Test_ParseFilePrecreateFilesOnlyFilesWithConstantSizes() + { + FILE *pFile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " CreateOnlyFilesWithConstantSizes\n" + "\n"); + fclose(pFile); + + XmlProfileParser p; + Profile profile; + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_IS_TRUE(profile.GetPrecreateFiles() == PrecreateFiles::OnlyFilesWithConstantSizes); + } + + void XmlProfileParserUnitTests::Test_ParseFilePrecreateFilesOnlyFilesWithConstantOrZeroSizes() + { + FILE *pFile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " CreateOnlyFilesWithConstantOrZeroSizes\n" + "\n"); + fclose(pFile); + + XmlProfileParser p; + Profile profile; + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_IS_TRUE(profile.GetPrecreateFiles() == PrecreateFiles::OnlyFilesWithConstantOrZeroSizes); + } + + void XmlProfileParserUnitTests::Test_ParseFileWriteBufferContentSequential() + { + FILE *pFile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " sequential\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"); + fclose(pFile); + + XmlProfileParser p; + Profile profile; + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + vector vTimespans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vTimespans.size(), (size_t)1); + vector vTargets(vTimespans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t = vTargets[0]; + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSize(), 0); + VERIFY_IS_TRUE(t.GetRandomDataWriteBufferSourcePath() == ""); + } + + void XmlProfileParserUnitTests::Test_ParseGroupAffinity() + { + // normal case + { + FILE *pFile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"); + fclose(pFile); + + XmlProfileParser p; + Profile profile; + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + vector vTimespans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vTimespans.size(), (size_t)1); + + const auto& vAffinity(vTimespans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)2); + VERIFY_ARE_EQUAL(vAffinity[0].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[0].bProc, (BYTE)2); + VERIFY_ARE_EQUAL(vAffinity[1].wGroup, 1); + VERIFY_ARE_EQUAL(vAffinity[1].bProc, (BYTE)32); + } + + // out-of-range processor index (BYTE) + { + FILE *pFile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"); + fclose(pFile); + + XmlProfileParser p; + Profile profile; + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + + // out-of-range group index (WORD) + { + FILE *pFile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"); + fclose(pFile); + + XmlProfileParser p; + Profile profile; + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + } + + void XmlProfileParserUnitTests::Test_ParseNonGroupAffinity() + { + FILE *pFile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 1\n" + " 31\n" + " \n" + " \n" + " \n" + "\n"); + fclose(pFile); + + XmlProfileParser p; + Profile profile; + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + vector vTimespans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vTimespans.size(), (size_t)1); + + // Old style, group is wired to zero. + const auto& vAffinity(vTimespans[0].GetAffinityAssignments()); + VERIFY_ARE_EQUAL(vAffinity.size(), (size_t)2); + VERIFY_ARE_EQUAL(vAffinity[0].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[0].bProc, (BYTE)1); + VERIFY_ARE_EQUAL(vAffinity[1].wGroup, 0); + VERIFY_ARE_EQUAL(vAffinity[1].bProc, (BYTE)31); + } + + void XmlProfileParserUnitTests::Test_ParseFileWriteBufferContentZero() + { + FILE *pFile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " zero\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"); + fclose(pFile); + + XmlProfileParser p; + Profile profile; + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + vector vTimespans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vTimespans.size(), (size_t)1); + vector vTargets(vTimespans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t = vTargets[0]; + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == true); + VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSize(), 0); + VERIFY_IS_TRUE(t.GetRandomDataWriteBufferSourcePath() == ""); + } + + void XmlProfileParserUnitTests::Test_ParseFileWriteBufferContentRandom() + { + FILE *pFile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, "\n" + "\n" + " \n" + " \n" + " true" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"); + fclose(pFile); + + XmlProfileParser p; + Profile profile; + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + vector vTimespans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vTimespans.size(), (size_t)1); + VERIFY_IS_TRUE(vTimespans[0].GetRandomWriteData() == true); + vector vTargets(vTimespans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t = vTargets[0]; + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSize(), 0); + VERIFY_IS_TRUE(t.GetRandomDataWriteBufferSourcePath() == ""); + } + + void XmlProfileParserUnitTests::Test_ParseFileWriteBufferContentRandomNoFilePath() + { + FILE *pFile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " random\n" + " \n" + " 223311\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"); + fclose(pFile); + + XmlProfileParser p; + Profile profile; + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + vector vTimespans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vTimespans.size(), (size_t)1); + vector vTargets(vTimespans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t = vTargets[0]; + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSize(), 223311); + VERIFY_IS_TRUE(t.GetRandomDataWriteBufferSourcePath() == ""); + } + + void XmlProfileParserUnitTests::Test_ParseFileWriteBufferContentRandomWithFilePath() + { + FILE *pFile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " random\n" + " \n" + " 223311\n" + " x:\\foo\\bar.dat\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"); + fclose(pFile); + + XmlProfileParser p; + Profile profile; + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + vector vTimespans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vTimespans.size(), (size_t)1); + vector vTargets(vTimespans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + Target t = vTargets[0]; + VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); + VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSize(), 223311); + VERIFY_IS_TRUE(t.GetRandomDataWriteBufferSourcePath() == "x:\\foo\\bar.dat"); + } + + void XmlProfileParserUnitTests::Test_ParseFileGlobalRequestCount() + { + FILE *pFile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, "\n" + "\n" + " \n" + " \n" + " 4\n" + " 6\n" + " \n" + " \n" + " \n" + " 100\n" + " \n" + " \n" + " 0\n" + " 200\n" + " \n" + " \n" + " 1\n" + " 50\n" + " \n" + " \n" + " 2\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 100\n" + " \n" + " \n" + " \n" + " \n" + "\n"); + fclose(pFile); + + XmlProfileParser p; + Profile profile; + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + vector vTimespans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vTimespans.size(), (size_t)1); + VERIFY_ARE_EQUAL(vTimespans[0].GetThreadCount(), (DWORD)4); + VERIFY_ARE_EQUAL(vTimespans[0].GetRequestCount(), (DWORD)6); + vector vTargets(vTimespans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)2); + VERIFY_ARE_EQUAL(vTargets[0].GetWeight(), (UINT32)100); + vector vThreadTargets(vTargets[0].GetThreadTargets()); + VERIFY_ARE_EQUAL(vThreadTargets.size(), (size_t)3); + VERIFY_ARE_EQUAL(vThreadTargets[0].GetThread(), (UINT32)0); + VERIFY_ARE_EQUAL(vThreadTargets[0].GetWeight(), (UINT32)200); + VERIFY_ARE_EQUAL(vThreadTargets[1].GetThread(), (UINT32)1); + VERIFY_ARE_EQUAL(vThreadTargets[1].GetWeight(), (UINT32)50); + VERIFY_ARE_EQUAL(vThreadTargets[2].GetThread(), (UINT32)2); + VERIFY_ARE_EQUAL(vThreadTargets[2].GetWeight(), (UINT32)0); + VERIFY_ARE_EQUAL(vTargets[1].GetWeight(), (UINT32)100); + VERIFY_ARE_EQUAL(vTargets[1].GetThreadTargets().size(), (size_t)0); + } + + void XmlProfileParserUnitTests::Test_ParseThroughput() + { + const PCHAR xmlDoc = "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " %s\n" + " %s\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + XmlProfileParser p; + FILE *pFile = nullptr; + + // + // Positive varations - units & default + // + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "131072", " unit=\"IOPS\"", "10"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetThroughputInBytesPerMillisecond(), (DWORD)((128*1024*10)/1000)); + } + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "131072", " unit=\"BPMS\"", "1500"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetThroughputInBytesPerMillisecond(), (DWORD)1500); + } + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "131072", "", "1024"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetThroughputInBytesPerMillisecond(), (DWORD)1024); + } + + // + // Negative variations - bad units / good units with bad data + // + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "131072", " unit=\"FRUIT\"", "1500"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "131072", " unit=\"IOPS\"", "FRUIT"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "131072", " unit=\"BPMS\"", "FRUIT"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "131072", "", "FRUIT"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + } + + void XmlProfileParserUnitTests::Test_ParseRandomSequentialMixed() + { + const PCHAR xmlDoc = "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "%s" // insert conflicts + " %s" // Random/RandomRatio + " \n" + " \n" + " \n" + " \n" + "\n"; + XmlProfileParser p; + FILE *pFile = nullptr; + + // Positive cases + + // Isolated + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "", "Ratio", "50", "Ratio"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetRandomRatio(), (UINT32) 50); + } + + // With + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "8192", "Ratio", "50", "Ratio"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetRandomRatio(), (UINT32) 50); + } + + // Negative, bounds validation + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "", "Ratio", "0", "Ratio"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "", "Ratio", "100", "Ratio"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "", "Ratio", "-100", "Ratio"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "", "Ratio", "200", "Ratio"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "", "Ratio", "junk", "Ratio"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + + // Negative, sequential conflict with RandomRatio + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "8192", "Ratio", "50", "Ratio"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + + // Negative, sequential conflict with Random + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "8192", "", "8192", ""); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + } + + // Template document for running distribution tests - type tags bracketing a type-specific insert + static const PCHAR xmlDistDoc = "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 65536\n" + " \n" + " <%s>\n" // type + " %s" // ranges + " \n" // type + " " + " \n" + " \n" + " \n" + " \n" + "\n"; + + void XmlProfileParserUnitTests::ValidateDistribution( + const PWCHAR desc, + boolean expectedParse, + boolean expectedValidate, + DistributionType type, + PCHAR xmlDoc, + const RangePair *insert, + const DistQuad *dist + ) + { + Profile profile; + XmlProfileParser p; + FILE *pFile = nullptr; + string xmlInsert; + + // Construct XML range list + for (UINT32 i = 0; i < insert->size; i++) + { + xmlInsert += "range[i].io); + xmlInsert += "\">"; + xmlInsert += to_string(insert->range[i].target); + xmlInsert += "\n"; + } + + // Bracketing typename - xmlDoc is assumed to be of the form ...[insert here]... + PCHAR typeName = nullptr; + + switch (type) + { + case DistributionType::Absolute: + typeName = "Absolute"; + break; + + case DistributionType::Percent: + typeName = "Percent"; + break; + + default: + assert(false); + break; + } + + // Generate content into temporary file and parse/validate + // Emit the description at parse validation for documentation in the output + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, typeName, xmlInsert.c_str(), typeName); + fclose(pFile); + + if (!expectedParse) + { + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule), desc); + return; // done + } + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule), desc); + + if (!expectedValidate) + { + VERIFY_IS_FALSE(profile.Validate(false)); + return; // done + } + + VERIFY_IS_TRUE(profile.Validate(false)); + auto t = profile.GetTimeSpans()[0].GetTargets()[0]; + auto dt = t.GetDistributionType(); + auto& v = t.GetDistributionRange(); + + VERIFY_ARE_EQUAL(type, dt); + VERIFY_ARE_EQUAL(dist->size, v.size()); + for (size_t i = 0; i < v.size(); ++i) + { + VERIFY_ARE_EQUAL(v[i]._src, dist->range[i].ioBase); + VERIFY_ARE_EQUAL(v[i]._span, dist->range[i].ioSpan); + VERIFY_ARE_EQUAL(v[i]._dst.first, dist->range[i].targetBase); + VERIFY_ARE_EQUAL(v[i]._dst.second, dist->range[i].targetSpan); + } + } + + void XmlProfileParserUnitTests::Test_ParseDistributionAbsolute() + { + // + // Positive cases - first matches the ResultParser UT + // + + { + // -rdabs10/1g:10/1g:0/100g + // hole + tail + const RangePair insert = { + 3, + {{10, 1*GB}, + {10, 1*GB}, + {0, 100*GB}} + }; + const DistQuad dist = { + 4, + {{0, 10, 0, 1*GB}, + {10, 10, 1*GB, 1*GB}, + {20, 0, 2*GB, 100*GB}, + {20, 80, 102*GB, 0}} + }; + + ValidateDistribution(L"Case 1", true, true, DistributionType::Absolute, xmlDistDoc, &insert, &dist); + } + + // + // Negative cases + // + + { + // -rdabs10/10 (note lack of units/small) + // same negative case in cmdlineparser + const RangePair insert = { + 1, + {{10, 10}} + }; + + ValidateDistribution(L"Case 2", true, false, DistributionType::Absolute, xmlDistDoc, &insert, nullptr); + } + { + // zero target not at end + const RangePair insert = { + 3, + {{10, 1*GB}, + {10, 0}, + {80, 1*GB}} + }; + + ValidateDistribution(L"Case 3", true, false, DistributionType::Absolute, xmlDistDoc, &insert, nullptr); + } + { + // -rdabs10/10G:80/10G:20:10G + // IO% overflow + const RangePair insert = { + 3, + {{10, 10*GB}, + {80, 10*GB}, + {20, 10*GB}} + }; + + ValidateDistribution(L"Case 4", true, false, DistributionType::Absolute, xmlDistDoc, &insert, nullptr); + } + } + + void XmlProfileParserUnitTests::Test_ParseDistributionPercent() + { + // + // Positive cases - first matches the ResultParser UT + // + + { + // -rdpct10/10:10/10:0/10 + // hole + tail + const RangePair insert = { + 3, + {{10, 10}, + {10, 10}, + {0, 10}} + }; + const DistQuad dist = { + 4, + {{0, 10, 0, 10}, + {10, 10, 10, 10}, + {20, 0, 20, 10}, + {20, 80, 30, 70}} + }; + + ValidateDistribution(L"Case 1", true, true, DistributionType::Percent, xmlDistDoc, &insert, &dist); + } + + // + // Negative cases + // + + { + // zero target not at end - fails parse (before validate) + const RangePair insert = { + 3, + {{10, 10}, + {10, 0}, + {80, 10}} + }; + + ValidateDistribution(L"Case 2", false, false, DistributionType::Percent, xmlDistDoc, &insert, nullptr); + } + { + // zero target at end - fails parse (before validate) + const RangePair insert = { + 2, + {{10, 10}, + {90, 0}} + }; + + ValidateDistribution(L"Case 3", false, false, DistributionType::Percent, xmlDistDoc, &insert, nullptr); + } + { + // IO% overflow / Target% OK + const RangePair insert = { + 3, + {{10, 10}, + {80, 10}, + {20, 80}} + }; + + ValidateDistribution(L"Case 4", true, false, DistributionType::Percent, xmlDistDoc, &insert, nullptr); + } + { + // IO% 100% / Target% incomplete + const RangePair insert = { + 3, + {{10, 10}, + {80, 10}, + {10, 10}} + }; + + ValidateDistribution(L"Case 5", true, false, DistributionType::Percent, xmlDistDoc, &insert, nullptr); + } + { + // IO% incomplete / Target% 100% + const RangePair insert = { + 3, + {{10, 10}, + {10, 80}, + {10, 10}} + }; + + ValidateDistribution(L"Case 6", true, false, DistributionType::Percent, xmlDistDoc, &insert, nullptr); + } + } + + void XmlProfileParserUnitTests::Test_ParseTemplateTargets() + { + const PCHAR xmlDocPre = \ + "\n" + "\n" + " \n"; + + const PCHAR xmlDocPost = \ + " \n" + "\n"; + + const PCHAR xmlTimeSpanPre = \ + "\n" + " \n"; + + const PCHAR xmlTimeSpanPost = \ + " \n" + "\n"; + + const PCHAR xmlTarget = \ + "\n" + " %s\n" + "\n"; + + XmlProfileParser p; + FILE *pFile = nullptr; + + // Non template baseline - null ptr pass for subst + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "foo.bin"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[0].GetPath().c_str(), "foo.bin")); + } + + // Non template baseline - empty pass for subst + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "foo.bin"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[0].GetPath().c_str(), "foo.bin")); + } + + // Template - 1 template, bad formats which will not parse (and will parse independent of subst) + + { + + char *cStrs[] = { "*", "*foo", "*1foo", "**1", "*-1" }; + + for (auto s : cStrs) + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, s); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + } + } + + // Template - 1 template, unsubst, will fail execution validation since !profile only + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + VERIFY_IS_FALSE(profile.Validate(false)); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[0].GetPath().c_str(), "*1")); + } + + // Template - 1 timespan 1 template, unsubst, will pass execution validation since profile only + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + profile.SetProfileOnly(true); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[0].GetPath().c_str(), "*1")); + } + + // Template - 1 timespan 1 template 1 subst - will pass execution validation + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + vTargets.emplace_back(); + vTargets[0].SetPath("foo.bin"); + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[0].GetPath().c_str(), "foo.bin")); + } + + // Template - 1 timespan 1 template 2 subst - will fail parse due to unused subst + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + vTargets.emplace_back(); + vTargets[0].SetPath("foo.bin"); + vTargets.emplace_back(); + vTargets[1].SetPath("bar.bin"); + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + } + + // 1 timespan 2 same template 1 subst + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + vTargets.emplace_back(); + vTargets[0].SetPath("foo.bin"); + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[0].GetPath().c_str(), "foo.bin")); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[1].GetPath().c_str(), "foo.bin")); + } + + // 1 timespan 2 template 2 subst + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTarget, "*2"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + vTargets.emplace_back(); + vTargets[0].SetPath("foo.bin"); + vTargets.emplace_back(); + vTargets[1].SetPath("bar.bin"); + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[0].GetPath().c_str(), "foo.bin")); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[1].GetPath().c_str(), "bar.bin")); + } + + // 1 timespan 2 template 1 subst - will fail since second template does not have a subst + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTarget, "*2"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + vTargets.emplace_back(); + vTargets[0].SetPath("foo.bin"); + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + } + + // 2 timespan same template 1 subst + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + vTargets.emplace_back(); + vTargets[0].SetPath("foo.bin"); + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[0].GetPath().c_str(), "foo.bin")); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[1].GetTargets()[0].GetPath().c_str(), "foo.bin")); + } + + // 2 timespan 1 template/ea 2 subst + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*2"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + vTargets.emplace_back(); + vTargets[0].SetPath("foo.bin"); + vTargets.emplace_back(); + vTargets[1].SetPath("bar.bin"); + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[0].GetPath().c_str(), "foo.bin")); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[1].GetTargets()[0].GetPath().c_str(), "bar.bin")); + } + } +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/UnitTests/XmlProfileParser/XmlProfileParser.UnitTests.h b/CristalDiskMark/source/diskspd22/UnitTests/XmlProfileParser/XmlProfileParser.UnitTests.h new file mode 100644 index 0000000..31eaafc --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/XmlProfileParser/XmlProfileParser.UnitTests.h @@ -0,0 +1,116 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#include "WexTestClass.h" +#include "XmlProfileParser.h" +#include +using namespace std; + +namespace UnitTests +{ + BEGIN_MODULE() + MODULE_PROPERTY(L"Feature", L"XmlProfileParser") + END_MODULE() + + MODULE_SETUP(ModuleSetup); + MODULE_CLEANUP(ModuleCleanup); + + class XmlProfileParserUnitTests : public WEX::TestClass + { + public: + TEST_CLASS(XmlProfileParserUnitTests) + + TEST_CLASS_SETUP(ClassSetup); + TEST_CLASS_CLEANUP(ClassCleanup); + + TEST_METHOD_SETUP(MethodSetup); + TEST_METHOD_CLEANUP(MethodCleanup); + + TEST_METHOD(Test_ParseDistributionAbsolute); + TEST_METHOD(Test_ParseDistributionPercent); + TEST_METHOD(Test_ParseFile); + TEST_METHOD(Test_ParseFileGlobalRequestCount); + TEST_METHOD(Test_ParseFilePrecreateFilesOnlyFilesWithConstantOrZeroSizes); + TEST_METHOD(Test_ParseFilePrecreateFilesOnlyFilesWithConstantSizes); + TEST_METHOD(Test_ParseFilePrecreateFilesUseMaxSize); + TEST_METHOD(Test_ParseFileWriteBufferContentRandom); + TEST_METHOD(Test_ParseFileWriteBufferContentRandomNoFilePath); + TEST_METHOD(Test_ParseFileWriteBufferContentRandomWithFilePath); + TEST_METHOD(Test_ParseFileWriteBufferContentSequential); + TEST_METHOD(Test_ParseFileWriteBufferContentZero); + TEST_METHOD(Test_ParseGroupAffinity); + TEST_METHOD(Test_ParseNonGroupAffinity); + TEST_METHOD(Test_ParseRandomSequentialMixed); + TEST_METHOD(Test_ParseTemplateTargets); + TEST_METHOD(Test_ParseThroughput); + + // + // Utility wrapping the specification and validation of a given distribution. + // + // Note that % and abs distributions are represented in the same way, only + // differing in the relative scale of the target spans. + // + + #pragma warning(push) + #pragma warning(disable:4200) // zero length array + + typedef struct { + UINT32 size; + struct { + UINT32 io; + UINT64 target; + } range[]; + } RangePair; + + typedef struct { + UINT32 size; + struct{ + UINT32 ioBase, ioSpan; + UINT64 targetBase, targetSpan; + } range[]; + } DistQuad; + + #pragma warning(pop) + + void ValidateDistribution( + const PWCHAR desc, + boolean expectedParseResult, + boolean expectedValidate, + DistributionType type, + PCHAR xmlDoc, + const RangePair *insert, + const DistQuad *dist + ); + + private: + string _sTempFilePath; + HMODULE _hModule; + }; +} diff --git a/CristalDiskMark/source/diskspd22/UnitTests/XmlProfileParser/XmlProfileParser.rc b/CristalDiskMark/source/diskspd22/UnitTests/XmlProfileParser/XmlProfileParser.rc new file mode 100644 index 0000000..b13c31b --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/XmlProfileParser/XmlProfileParser.rc @@ -0,0 +1,19 @@ +#include +#include "Version.h" + +DISKSPD.XSD HTML "..\\XmlProfileParser\\diskspd.xsd" + +#include + +#define VER_FILETYPE VFT_APP +#define VER_FILESUBTYPE VFT2_UNKNOWN +#define VER_FILEDESCRIPTION_STR "DiskSpd Storage Performance Tool" +#define VER_INTERNALNAME_STR "XmlProfileParser.UnitTests.dll" + +#undef VER_PRODUCTVERSION +#define VER_PRODUCTVERSION DISKSPD_MAJOR,DISKSPD_MINOR,DISKSPD_BUILD,DISKSPD_QFE + +#undef VER_PRODUCTVERSION_STR +#define VER_PRODUCTVERSION_STR DISKSPD_NUMERIC_VERSION_STRING + +#include "common.ver" diff --git a/CristalDiskMark/source/diskspd22/UnitTests/XmlProfileParser/stdafx.h b/CristalDiskMark/source/diskspd22/UnitTests/XmlProfileParser/stdafx.h new file mode 100644 index 0000000..a79da8d --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/XmlProfileParser/stdafx.h @@ -0,0 +1,32 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + +#include \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/UnitTests/XmlResultParser/XmlResultParser.UnitTests.cpp b/CristalDiskMark/source/diskspd22/UnitTests/XmlResultParser/XmlResultParser.UnitTests.cpp new file mode 100644 index 0000000..2accbde --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/XmlResultParser/XmlResultParser.UnitTests.cpp @@ -0,0 +1,464 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "StdAfx.h" +#include "XmlResultParser.UnitTests.h" +#include "Common.h" +#include "xmlresultparser.h" +#include +#include + +using namespace WEX::TestExecution; +using namespace WEX::Logging; +using namespace std; + +namespace UnitTests +{ + void XmlResultParserUnitTests::Test_ParseResults() + { + Profile profile; + TimeSpan timeSpan; + Target target; + XmlResultParser parser; + + Results results; + results.fUseETW = false; + double fTime = 120.0; + results.ullTimeCount = PerfTimer::SecondsToPerfTime(fTime); + + // First group has 1 active cpu + // 30% user, 45% idle, 25% non-idle kernel (45% + 25% = 70%) + SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION systemProcessorInfo = { 0 }; + systemProcessorInfo.UserTime.QuadPart = static_cast(fTime * 30 * 100000); + systemProcessorInfo.IdleTime.QuadPart = static_cast(fTime * 45 * 100000); + systemProcessorInfo.KernelTime.QuadPart = static_cast(fTime * 70 * 100000); + results.vSystemProcessorPerfInfo.push_back(systemProcessorInfo); + + // Second group has 2 active + // 100% idle + systemProcessorInfo.UserTime.QuadPart = static_cast(fTime * 0 * 100000); + systemProcessorInfo.IdleTime.QuadPart = static_cast(fTime * 100 * 100000); + systemProcessorInfo.KernelTime.QuadPart = static_cast(fTime * 100 * 100000); + results.vSystemProcessorPerfInfo.push_back(systemProcessorInfo); + results.vSystemProcessorPerfInfo.push_back(systemProcessorInfo); + + // TODO: multiple target cases, full profile/result variations + target.SetPath("testfile1.dat"); + target.SetCacheMode(TargetCacheMode::DisableOSCache); + target.SetWriteThroughMode(WriteThroughMode::On); + target.SetThroughputIOPS(1000); + + timeSpan.AddTarget(target); + timeSpan.SetCalculateIopsStdDev(true); + + TargetResults targetResults; + targetResults.sPath = "testfile1.dat"; + targetResults.ullFileSize = 10 * 1024 * 1024; + targetResults.ullReadBytesCount = 4 * 1024 * 1024; + targetResults.ullReadIOCount = 6; + targetResults.ullWriteBytesCount = 2 * 1024 * 1024; + targetResults.ullWriteIOCount = 10; + targetResults.ullBytesCount = targetResults.ullReadBytesCount + targetResults.ullWriteBytesCount; + targetResults.ullIOCount = targetResults.ullReadIOCount + targetResults.ullWriteIOCount; + + // TODO: Histogram readLatencyHistogram; + // TODO: Histogram writeLatencyHistogram; + // TODO: IoBucketizer writeBucketizer; + + targetResults.readBucketizer.Initialize(1000, timeSpan.GetDuration()); + for (size_t i = 0; i < timeSpan.GetDuration(); i++) + { + // add an io halfway through the bucket's time interval + targetResults.readBucketizer.Add(i*1000 + 500, 0); + } + + ThreadResults threadResults; + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + + vector vResults; + vResults.push_back(results); + + // Just throw away the computername, pp and reset the timestamp - for the ut, it's + // as useful (and simpler) to verify statics as anything else. Reconstruct the + // processor topo to a fixed example as well. Note that the performance + // efficiency class must be placed since it is calculated on the fly during + // the actual GLPIEx enumeration. If we could shim GLPIEx ... + SystemInformation system; + system.ResetTime(); + system.sComputerName.clear(); + system.sActivePolicyName.clear(); + system.sActivePolicyGuid.clear(); + + system.processorTopology._ulProcessorCount = 3; + system.processorTopology._ubPerformanceEfficiencyClass = 1; + system.processorTopology._fSMT = true; + + system.processorTopology._vProcessorGroupInformation.clear(); + system.processorTopology._vProcessorGroupInformation.emplace_back((WORD)0, (BYTE)1, (BYTE)1, (KAFFINITY)0x1); + system.processorTopology._vProcessorGroupInformation.emplace_back((WORD)1, (BYTE)4, (BYTE)2, (KAFFINITY)0x3); + + ProcessorNumaInformation node; + node._nodeNumber = 0; + node._vProcessorMasks.emplace_back((WORD)0, (KAFFINITY)0x1); + node._vProcessorMasks.emplace_back((WORD)1, (KAFFINITY)0x3); + system.processorTopology._vProcessorNumaInformation.clear(); + system.processorTopology._vProcessorNumaInformation.push_back(node); + + ProcessorSocketInformation socket; + socket._vProcessorMasks.emplace_back((WORD)0, (KAFFINITY)0x1); + socket._vProcessorMasks.emplace_back((WORD)1, (KAFFINITY)0x3); + system.processorTopology._vProcessorSocketInformation.clear(); + system.processorTopology._vProcessorSocketInformation.push_back(socket); + + system.processorTopology._vProcessorCoreInformation.clear(); + system.processorTopology._vProcessorCoreInformation.emplace_back((WORD)0, (KAFFINITY)0x1, (BYTE)0); + system.processorTopology._vProcessorCoreInformation.emplace_back((WORD)1, (KAFFINITY)0x3, (BYTE)1); + + // finally, add the timespan to the profile and dump. + profile.AddTimeSpan(timeSpan); + + string sResults = parser.ParseResults(profile, system, vResults); + + // stringify random text, quoting "'s and adding newline/preserving tabs + // gc some.txt |% { write-host $("`"{0}\n`"" -f $($_ -replace "`"","\`"" -replace "`t","\t")) } + + const char *pcszExpectedOutput = \ + "\n" + " \n" + " \n" + " \n" + " " DISKSPD_NUMERIC_VERSION_STRING "\n" + " " DISKSPD_DATE_VERSION_STRING "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 0\n" + " text\n" + " false\n" + " \n" + " \n" + " false\n" + " false\n" + " true\n" + " false\n" + " 10\n" + " 5\n" + " 0\n" + " 0\n" + " 0\n" + " 1000\n" + " 0\n" + " \n" + " \n" + " testfile1.dat\n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " true\n" + " true\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 1000\n" + " 1\n" + " 3\n" + " 1\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 120.00\n" + " 1\n" + " 0\n" + " 3\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 0\n" + " 0\n" + " 0\n" + " 0\n" + " 55.00\n" + " 30.00\n" + " 25.00\n" + " 45.00\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 1\n" + " 0\n" + " 1\n" + " 0\n" + " 0.00\n" + " 0.00\n" + " 0.00\n" + " 100.00\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 1\n" + " 0\n" + " 1\n" + " 1\n" + " 0.00\n" + " 0.00\n" + " 0.00\n" + " 100.00\n" + " \n" + " \n" + " 18.33\n" + " 10.00\n" + " 8.33\n" + " 81.67\n" + " \n" + " \n" + " \n" + " 0.000\n" + " 0.000\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 0\n" + " \n" + " testfile1.dat\n" + " 6291456\n" + " 10485760\n" + " 16\n" + " 4194304\n" + " 6\n" + " 2097152\n" + " 10\n" + " \n" + " 0.000\n" + " 0.000\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""; + +#if 0 + HANDLE h; + DWORD written; + h = CreateFileW(L"g:\\xmlresult-received.txt", GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + WriteFile(h, sResults.c_str(), (DWORD)sResults.length(), &written, NULL); + VERIFY_ARE_EQUAL(sResults.length(), written); + CloseHandle(h); + + h = CreateFileW(L"g:\\xmlresult-expected.txt", GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + WriteFile(h, pcszExpectedOutput, (DWORD)strlen(pcszExpectedOutput), &written, NULL); + VERIFY_ARE_EQUAL((DWORD)strlen(pcszExpectedOutput), written); + CloseHandle(h); + + printf("--\n%s\n", sResults.c_str()); + printf("-------------------------------------------------\n"); + printf("--\n%s\n", pcszExpectedOutput); +#endif + + VERIFY_ARE_EQUAL(0, strcmp(sResults.c_str(), pcszExpectedOutput)); + } + + void XmlResultParserUnitTests::Test_ParseProfile() + { + Profile profile; + XmlResultParser parser; + TimeSpan timeSpan; + Target target; + + timeSpan.AddTarget(target); + profile.AddTimeSpan(timeSpan); + + string s = parser.ParseProfile(profile); + const char *pcszExpectedOutput = "\n" + " 0\n" + " text\n" + " false\n" + " \n" + " \n" + " false\n" + " false\n" + " false\n" + " false\n" + " 10\n" + " 5\n" + " 0\n" + " 0\n" + " 0\n" + " 1000\n" + " 0\n" + " \n" + " \n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + + //VERIFY_ARE_EQUAL(pcszExpectedOutput, s.c_str()); + VERIFY_ARE_EQUAL(strlen(pcszExpectedOutput), s.length()); + VERIFY_IS_TRUE(!strcmp(pcszExpectedOutput, s.c_str())); + } + + void XmlResultParserUnitTests::Test_ParseTargetProfile() + { + Target target; + string sResults; + char pszExpectedOutput[4096]; + int nWritten; + + const char *pcszOutputTemplate = \ + "\n" + " testfile1.dat\n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " true\n" + " true\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " %s\n" // 2 param + " 1\n" + " 3\n" + " 1\n" + "\n"; + + target.SetPath("testfile1.dat"); + target.SetCacheMode(TargetCacheMode::DisableOSCache); + target.SetWriteThroughMode(WriteThroughMode::On); + + // Base case - no limit + + nWritten = sprintf_s(pszExpectedOutput, sizeof(pszExpectedOutput), + pcszOutputTemplate, "", "0"); + VERIFY_IS_GREATER_THAN(nWritten, 0); + sResults = target.GetXml(0); + VERIFY_ARE_EQUAL(sResults, pszExpectedOutput); + + // IOPS - with units + + target.SetThroughputIOPS(1000); + nWritten = sprintf_s(pszExpectedOutput, sizeof(pszExpectedOutput), + pcszOutputTemplate, " unit=\"IOPS\"", "1000"); + VERIFY_IS_GREATER_THAN(nWritten, 0); + sResults = target.GetXml(0); + VERIFY_ARE_EQUAL(sResults, pszExpectedOutput); + + // BPMS - not specified with units in output + + target.SetThroughput(1000); + nWritten = sprintf_s(pszExpectedOutput, sizeof(pszExpectedOutput), + pcszOutputTemplate, "", "1000"); + VERIFY_IS_GREATER_THAN(nWritten, 0); + sResults = target.GetXml(0); + VERIFY_ARE_EQUAL(sResults, pszExpectedOutput); + } +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/UnitTests/XmlResultParser/XmlResultParser.UnitTests.h b/CristalDiskMark/source/diskspd22/UnitTests/XmlResultParser/XmlResultParser.UnitTests.h new file mode 100644 index 0000000..acebf22 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/XmlResultParser/XmlResultParser.UnitTests.h @@ -0,0 +1,47 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#include "WexTestClass.h" + +namespace UnitTests +{ + BEGIN_MODULE() + MODULE_PROPERTY(L"Feature", L"XmlResultParser") + END_MODULE() + + class XmlResultParserUnitTests : public WEX::TestClass + { + public: + TEST_CLASS(XmlResultParserUnitTests); + TEST_METHOD(Test_ParseResults); + TEST_METHOD(Test_ParseProfile); + TEST_METHOD(Test_ParseTargetProfile); + }; +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/UnitTests/XmlResultParser/XmlResultParser.rc b/CristalDiskMark/source/diskspd22/UnitTests/XmlResultParser/XmlResultParser.rc new file mode 100644 index 0000000..286016e --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/XmlResultParser/XmlResultParser.rc @@ -0,0 +1,17 @@ +#include +#include "Version.h" + +#include + +#define VER_FILETYPE VFT_APP +#define VER_FILESUBTYPE VFT2_UNKNOWN +#define VER_FILEDESCRIPTION_STR "DiskSpd Storage Performance Tool" +#define VER_INTERNALNAME_STR "XmlResultParser.UnitTests.dll" + +#undef VER_PRODUCTVERSION +#define VER_PRODUCTVERSION DISKSPD_MAJOR,DISKSPD_MINOR,DISKSPD_BUILD,DISKSPD_QFE + +#undef VER_PRODUCTVERSION_STR +#define VER_PRODUCTVERSION_STR DISKSPD_NUMERIC_VERSION_STRING + +#include "common.ver" diff --git a/CristalDiskMark/source/diskspd22/UnitTests/XmlResultParser/stdafx.h b/CristalDiskMark/source/diskspd22/UnitTests/XmlResultParser/stdafx.h new file mode 100644 index 0000000..a79da8d --- /dev/null +++ b/CristalDiskMark/source/diskspd22/UnitTests/XmlResultParser/stdafx.h @@ -0,0 +1,32 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + +#include \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/XmlProfileParser/XmlProfileParser.cpp b/CristalDiskMark/source/diskspd22/XmlProfileParser/XmlProfileParser.cpp new file mode 100644 index 0000000..be75dc6 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/XmlProfileParser/XmlProfileParser.cpp @@ -0,0 +1,1533 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "XmlProfileParser.h" +#include +#include +#include +#include + +HRESULT ReportXmlError( + const char *pszName, + IXMLDOMParseError *pXMLError + ) +{ + long line; + long linePos; + long errorCode = E_FAIL; + CComBSTR bReason; + BSTR bstr; + HRESULT hr; + + hr = pXMLError->get_line(&line); + if (FAILED(hr)) + { + line = 0; + } + hr = pXMLError->get_linepos(&linePos); + if (FAILED(hr)) + { + linePos = 0; + } + hr = pXMLError->get_errorCode(&errorCode); + if (FAILED(hr)) + { + errorCode = E_FAIL; + } + hr = pXMLError->get_reason(&bstr); + if (SUCCEEDED(hr)) + { + bReason.Attach(bstr); + } + + fprintf(stderr, + "ERROR: failed to load %s, line %lu, line position %lu, errorCode %08x\nERROR: reason: %S\n", + pszName, line, linePos, errorCode, (PWCHAR)bReason); + + return errorCode; +} + +bool XmlProfileParser::ParseFile(const char *pszPath, Profile *pProfile, vector *pvSubstTargets, HMODULE hModule) +{ + assert(pszPath != nullptr); + assert(pProfile != nullptr); + + // import schema from the named resource + HRSRC hSchemaXmlResource = FindResource(hModule, L"DISKSPD.XSD", RT_HTML); + assert(hSchemaXmlResource != NULL); + HGLOBAL hSchemaXml = LoadResource(hModule, hSchemaXmlResource); + assert(hSchemaXml != NULL); + LPVOID pSchemaXml = LockResource(hSchemaXml); + assert(pSchemaXml != NULL); + + // convert from utf-8 produced by the xsd authoring tool to utf-16 + int cchSchemaXml = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)pSchemaXml, -1, NULL, 0); + vector vWideSchemaXml(cchSchemaXml); + int dwcchWritten = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)pSchemaXml, -1, vWideSchemaXml.data(), cchSchemaXml); + UNREFERENCED_PARAMETER(dwcchWritten); + assert(dwcchWritten == cchSchemaXml); + // ... and finally, packed in a bstr for the loadXml interface + CComBSTR bSchemaXml(vWideSchemaXml.data()); + + bool fComInitialized = false; + HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + if (SUCCEEDED(hr)) + { + fComInitialized = true; + CComPtr spXmlDoc = nullptr; + CComPtr spXmlSchema = nullptr; + CComPtr spXmlSchemaColl = nullptr; + CComPtr spXmlParseError = nullptr; + + // create com objects and decorate + hr = CoCreateInstance(__uuidof(DOMDocument60), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spXmlSchema)); + if (SUCCEEDED(hr)) + { + hr = spXmlSchema->put_async(VARIANT_FALSE); + } + if (SUCCEEDED(hr)) + { + hr = spXmlSchema->setProperty(CComBSTR("ProhibitDTD"), CComVariant(VARIANT_FALSE)); + } + if (SUCCEEDED(hr)) + { + hr = CoCreateInstance(__uuidof(XMLSchemaCache60), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spXmlSchemaColl)); + } + if (SUCCEEDED(hr)) + { + hr = spXmlSchemaColl->put_validateOnLoad(VARIANT_TRUE); + } + if (SUCCEEDED(hr)) + { + hr = CoCreateInstance(__uuidof(DOMDocument60), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spXmlDoc)); + } + if (SUCCEEDED(hr)) + { + hr = spXmlDoc->put_async(VARIANT_FALSE); + } + if (SUCCEEDED(hr)) + { + hr = spXmlDoc->put_validateOnParse(VARIANT_TRUE); + } + if (SUCCEEDED(hr)) + { + VARIANT_BOOL fvIsOk; + hr = spXmlSchema->loadXML(bSchemaXml, &fvIsOk); + if (FAILED(hr) || fvIsOk != VARIANT_TRUE) + { + hr = spXmlSchema->get_parseError(&spXmlParseError); + if (SUCCEEDED(hr)) + { + ReportXmlError("schema", spXmlParseError); + } + hr = E_FAIL; + } + } + if (SUCCEEDED(hr)) + { + CComVariant vXmlSchema(spXmlSchema); + CComBSTR bNull(""); + hr = spXmlSchemaColl->add(bNull, vXmlSchema); + } + if (SUCCEEDED(hr)) + { + CComVariant vSchemaCache(spXmlSchemaColl); + hr = spXmlDoc->putref_schemas(vSchemaCache); + } + if (SUCCEEDED(hr)) + { + VARIANT_BOOL fvIsOk; + CComVariant vPath(pszPath); + hr = spXmlDoc->load(vPath, &fvIsOk); + if (FAILED(hr) || fvIsOk != VARIANT_TRUE) + { + hr = spXmlDoc->get_parseError(&spXmlParseError); + if (SUCCEEDED(hr)) + { + ReportXmlError("profile", spXmlParseError); + } + hr = E_FAIL; + } + } + + // + // XML has now passed basic schema validation. Bulld the target substitutions and parse the profile. + // + + vector> vSubsts; + + if (pvSubstTargets) + { + for (auto target : *pvSubstTargets) + { + vSubsts.emplace_back(make_pair(target.GetPath(), false)); + } + } + + if (SUCCEEDED(hr)) + { + bool b; + hr = _GetBool(spXmlDoc, "//Profile/Verbose", &b); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetVerbose(b); + } + } + + if (SUCCEEDED(hr)) + { + bool b; + hr = _GetBool(spXmlDoc, "//Profile/VerboseStats", &b); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetVerboseStats(b); + } + } + + if (SUCCEEDED(hr)) + { + DWORD i; + hr = _GetDWORD(spXmlDoc, "//Profile/ExperimentFlags", &i); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + g_ExperimentFlags = i; + } + } + + if (SUCCEEDED(hr)) + { + DWORD i; + hr = _GetDWORD(spXmlDoc, "//Profile/Progress", &i); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetProgress(i); + } + } + + if (SUCCEEDED(hr)) + { + string sResultFormat; + hr = _GetString(spXmlDoc, "//Profile/ResultFormat", &sResultFormat); + if (SUCCEEDED(hr) && (hr != S_FALSE) && sResultFormat == "xml") + { + pProfile->SetResultsFormat(ResultsFormat::Xml); + } + } + + if (SUCCEEDED(hr)) + { + string sCreateFiles; + hr = _GetString(spXmlDoc, "//Profile/PrecreateFiles", &sCreateFiles); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + if (sCreateFiles == "UseMaxSize") + { + pProfile->SetPrecreateFiles(PrecreateFiles::UseMaxSize); + } + else if (sCreateFiles == "CreateOnlyFilesWithConstantSizes") + { + pProfile->SetPrecreateFiles(PrecreateFiles::OnlyFilesWithConstantSizes); + } + else if (sCreateFiles == "CreateOnlyFilesWithConstantOrZeroSizes") + { + pProfile->SetPrecreateFiles(PrecreateFiles::OnlyFilesWithConstantOrZeroSizes); + } + else + { + hr = E_INVALIDARG; + } + } + } + + if (SUCCEEDED(hr)) + { + hr = _ParseEtw(spXmlDoc, pProfile); + } + + if (SUCCEEDED(hr)) + { + hr = _ParseTimeSpans(spXmlDoc, pProfile, vSubsts); + } + + // + // Error on unused substitutions - user should ensure these match up. + // + // Note that no (zero) substitutions are OK at the point of parsing, which allows + // for -Rp forms on template profiles. Validation for executed profiles will occur + // later during common validation. + // + // Generate an error for each unused substitution. + // + + if (SUCCEEDED(hr)) + { + for (size_t i = 1; i <= vSubsts.size(); ++i) + { + if (!vSubsts[i - 1].second) + { + fprintf(stderr, "ERROR: unused template target substitution _%u -> %s - check profile\n", (int) i, vSubsts[i - 1].first.c_str()); + hr = E_INVALIDARG; + } + } + } + } + + if (fComInitialized) + { + CoUninitialize(); + } + + return SUCCEEDED(hr); +} + +HRESULT XmlProfileParser::_ParseEtw(IXMLDOMDocument2 *pXmlDoc, Profile *pProfile) +{ + bool fEtwProcess; + HRESULT hr = _GetBool(pXmlDoc, "//Profile/ETW/Process", &fEtwProcess); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwProcess(fEtwProcess); + } + + if (SUCCEEDED(hr)) + { + bool fEtwThread; + hr = _GetBool(pXmlDoc, "//Profile/ETW/Thread", &fEtwThread); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwThread(fEtwThread); + } + } + + if (SUCCEEDED(hr)) + { + bool fEtwImageLoad; + hr = _GetBool(pXmlDoc, "//Profile/ETW/ImageLoad", &fEtwImageLoad); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwImageLoad(fEtwImageLoad); + } + } + + if (SUCCEEDED(hr)) + { + bool fEtwDiskIO; + hr = _GetBool(pXmlDoc, "//Profile/ETW/DiskIO", &fEtwDiskIO); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwDiskIO(fEtwDiskIO); + } + } + + if (SUCCEEDED(hr)) + { + bool fEtwMemoryPageFaults; + hr = _GetBool(pXmlDoc, "//Profile/ETW/MemoryPageFaults", &fEtwMemoryPageFaults); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwMemoryPageFaults(fEtwMemoryPageFaults); + } + } + + if (SUCCEEDED(hr)) + { + bool fEtwMemoryHardFaults; + hr = _GetBool(pXmlDoc, "//Profile/ETW/MemoryHardFaults", &fEtwMemoryHardFaults); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwMemoryHardFaults(fEtwMemoryHardFaults); + } + } + + if (SUCCEEDED(hr)) + { + bool fEtwNetwork; + hr = _GetBool(pXmlDoc, "//Profile/ETW/Network", &fEtwNetwork); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwNetwork(fEtwNetwork); + } + } + + if (SUCCEEDED(hr)) + { + bool fEtwRegistry; + hr = _GetBool(pXmlDoc, "//Profile/ETW/Registry", &fEtwRegistry); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwRegistry(fEtwRegistry); + } + } + + if (SUCCEEDED(hr)) + { + bool fEtwUsePagedMemory; + hr = _GetBool(pXmlDoc, "//Profile/ETW/UsePagedMemory", &fEtwUsePagedMemory); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwUsePagedMemory(fEtwUsePagedMemory); + } + } + + if (SUCCEEDED(hr)) + { + bool fEtwUsePerfTimer; + hr = _GetBool(pXmlDoc, "//Profile/ETW/UsePerfTimer", &fEtwUsePerfTimer); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwUsePerfTimer(fEtwUsePerfTimer); + } + } + + if (SUCCEEDED(hr)) + { + bool fEtwUseSystemTimer; + hr = _GetBool(pXmlDoc, "//Profile/ETW/UseSystemTimer", &fEtwUseSystemTimer); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwUseSystemTimer(fEtwUseSystemTimer); + } + } + + if (SUCCEEDED(hr)) + { + bool fEtwUseCyclesCounter; + hr = _GetBool(pXmlDoc, "//Profile/ETW/UseCyclesCounter", &fEtwUseCyclesCounter); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwUseCyclesCounter(fEtwUseCyclesCounter); + } + } + + return hr; +} + +HRESULT XmlProfileParser::_ParseTimeSpans(IXMLDOMDocument2 *pXmlDoc, Profile *pProfile, vector>& vSubsts) +{ + CComPtr spNodeList = nullptr; + CComVariant query("//Profile/TimeSpans/TimeSpan"); + HRESULT hr = pXmlDoc->selectNodes(query.bstrVal, &spNodeList); + if (SUCCEEDED(hr)) + { + long cNodes; + hr = spNodeList->get_length(&cNodes); + if (SUCCEEDED(hr)) + { + for (int i = 0; i < cNodes; i++) + { + CComPtr spNode = nullptr; + hr = spNodeList->get_item(i, &spNode); + if (SUCCEEDED(hr)) + { + TimeSpan timeSpan; + hr = _ParseTimeSpan(spNode, &timeSpan, vSubsts); + if (SUCCEEDED(hr)) + { + pProfile->AddTimeSpan(timeSpan); + } + } + } + } + } + + return hr; +} + +HRESULT XmlProfileParser::_ParseTimeSpan(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan, vector>& vSubsts) +{ + UINT32 ulDuration; + HRESULT hr = _GetUINT32(pXmlNode, "Duration", &ulDuration); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetDuration(ulDuration); + } + + if (SUCCEEDED(hr)) + { + UINT32 ulWarmup; + hr = _GetUINT32(pXmlNode, "Warmup", &ulWarmup); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetWarmup(ulWarmup); + } + } + + if (SUCCEEDED(hr)) + { + UINT32 ulCooldown; + hr = _GetUINT32(pXmlNode, "Cooldown", &ulCooldown); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetCooldown(ulCooldown); + } + } + + if (SUCCEEDED(hr)) + { + UINT32 ulRandSeed; + hr = _GetUINT32(pXmlNode, "RandSeed", &ulRandSeed); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetRandSeed(ulRandSeed); + } + } + + if (SUCCEEDED(hr)) + { + bool fRandomWriteData; + hr = _GetBool(pXmlNode, "RandomWriteData", &fRandomWriteData); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetRandomWriteData(fRandomWriteData); + } + } + + if (SUCCEEDED(hr)) + { + UINT32 ulThreadCount; + hr = _GetUINT32(pXmlNode, "ThreadCount", &ulThreadCount); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetThreadCount(ulThreadCount); + } + } + + if (SUCCEEDED(hr)) + { + UINT32 ulRequestCount; + hr = _GetUINT32(pXmlNode, "RequestCount", &ulRequestCount); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetRequestCount(ulRequestCount); + } + } + + if (SUCCEEDED(hr)) + { + bool fDisableAffinity; + hr = _GetBool(pXmlNode, "DisableAffinity", &fDisableAffinity); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetDisableAffinity(fDisableAffinity); + } + } + + if (SUCCEEDED(hr)) + { + bool fCompletionRoutines; + hr = _GetBool(pXmlNode, "CompletionRoutines", &fCompletionRoutines); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetCompletionRoutines(fCompletionRoutines); + } + } + + if (SUCCEEDED(hr)) + { + bool fMeasureLatency; + hr = _GetBool(pXmlNode, "MeasureLatency", &fMeasureLatency); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetMeasureLatency(fMeasureLatency); + } + } + + if (SUCCEEDED(hr)) + { + bool fCalculateIopsStdDev; + hr = _GetBool(pXmlNode, "CalculateIopsStdDev", &fCalculateIopsStdDev); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetCalculateIopsStdDev(fCalculateIopsStdDev); + } + } + + if (SUCCEEDED(hr)) + { + UINT32 ulIoBucketDuration; + hr = _GetUINT32(pXmlNode, "IoBucketDuration", &ulIoBucketDuration); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetIoBucketDurationInMilliseconds(ulIoBucketDuration); + } + } + + // Look for downlevel non-group aware assignment + if (SUCCEEDED(hr)) + { + hr = _ParseAffinityAssignment(pXmlNode, pTimeSpan); + } + + // Look for uplevel group aware assignment. + if (SUCCEEDED(hr)) + { + hr = _ParseAffinityGroupAssignment(pXmlNode, pTimeSpan); + } + + if (SUCCEEDED(hr)) + { + hr = _ParseTargets(pXmlNode, pTimeSpan, vSubsts); + } + + return hr; +} + +HRESULT XmlProfileParser::_SubstTarget(Target *pTarget, vector>& vSubsts) +{ + auto& sPath = pTarget->GetPath(); + + if (sPath.length() >= 1 && sPath[0] == TEMPLATE_TARGET_PREFIX) + { + auto str = sPath.c_str(); + char *strend; + ULONG i; + + // + // Note that we will parse all template targets for correctness but allow empty substuttion lists + // to pass through. If this profile will be executed, validation of paths will catch unsubst template targets + // + // strtoul will accept signed integers (e.g., -1), so we need to add our explicit assertion that this is indeed a digit. + // + + i = strtoul(str + 1, &strend, 10); + + if (i == 0 || *strend != '\0' || !isdigit(*(str + 1))) + { + fprintf(stderr, "ERROR: template path '%s' is not a valid path reference - must be %c - check profile\n", str, TEMPLATE_TARGET_PREFIX); + return E_INVALIDARG; + } + + if (vSubsts.size() != 0) + { + if (i > vSubsts.size()) + { + fprintf(stderr, "ERROR: template path '%s' does not have a specified substitution - check profile\n", str); + return E_INVALIDARG; + } + + // + // Substitute this target, indicating this substitution was used (for validation). + // Note 1 based count and 0 based index. + // + + pTarget->SetPath(vSubsts[i - 1].first); + vSubsts[i - 1].second = true; + } + } + + return S_OK; +} + +HRESULT XmlProfileParser::_ParseTargets(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan, vector>& vSubsts) +{ + CComVariant query("Targets/Target"); + CComPtr spNodeList = nullptr; + HRESULT hr = pXmlNode->selectNodes(query.bstrVal, &spNodeList); + if (SUCCEEDED(hr)) + { + long cNodes; + hr = spNodeList->get_length(&cNodes); + if (SUCCEEDED(hr)) + { + for (int i = 0; i < cNodes; i++) + { + CComPtr spNode = nullptr; + hr = spNodeList->get_item(i, &spNode); + if (SUCCEEDED(hr)) + { + Target target; + hr = _ParseTarget(spNode, &target); + if (SUCCEEDED(hr)) + { + hr = _SubstTarget(&target, vSubsts); + } + if (SUCCEEDED(hr)) + { + pTimeSpan->AddTarget(target); + } + } + + if (!SUCCEEDED(hr)) + { + break; + } + } + } + } + return hr; +} + +HRESULT XmlProfileParser::_ParseRandomDataSource(IXMLDOMNode *pXmlNode, Target *pTarget) +{ + CComPtr spNodeList = nullptr; + CComVariant query("RandomDataSource"); + HRESULT hr = pXmlNode->selectNodes(query.bstrVal, &spNodeList); + if (SUCCEEDED(hr)) + { + long cNodes; + hr = spNodeList->get_length(&cNodes); + if (SUCCEEDED(hr) && (cNodes == 1)) + { + CComPtr spNode = nullptr; + hr = spNodeList->get_item(0, &spNode); + if (SUCCEEDED(hr)) + { + UINT64 cb; + hr = _GetUINT64(spNode, "SizeInBytes", &cb); + if (SUCCEEDED(hr) && (S_FALSE != hr)) + { + pTarget->SetRandomDataWriteBufferSize(cb); + + string sPath; + hr = _GetString(spNode, "FilePath", &sPath); + if (SUCCEEDED(hr) && (S_FALSE != hr)) + { + pTarget->SetRandomDataWriteBufferSourcePath(sPath); + } + } + } + } + } + return hr; +} + +HRESULT XmlProfileParser::_ParseWriteBufferContent(IXMLDOMNode *pXmlNode, Target *pTarget) +{ + CComPtr spNodeList = nullptr; + CComVariant query("WriteBufferContent"); + HRESULT hr = pXmlNode->selectNodes(query.bstrVal, &spNodeList); + if (SUCCEEDED(hr)) + { + long cNodes; + hr = spNodeList->get_length(&cNodes); + if (SUCCEEDED(hr) && (cNodes == 1)) + { + CComPtr spNode = nullptr; + hr = spNodeList->get_item(0, &spNode); + if (SUCCEEDED(hr)) + { + string sPattern; + hr = _GetString(spNode, "Pattern", &sPattern); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + if (sPattern == "sequential") + { + // that's the default option - do nothing + } + else if (sPattern == "zero") + { + pTarget->SetZeroWriteBuffers(true); + } + else if (sPattern == "random") + { + hr = _ParseRandomDataSource(spNode, pTarget); + } + else + { + hr = E_INVALIDARG; + } + } + } + } + } + return hr; +} + +HRESULT XmlProfileParser::_ParseTarget(IXMLDOMNode *pXmlNode, Target *pTarget) +{ + // For enforcement of sequential/random conflicts. + // This is simplified for the XML since we control parse order. + bool fSequential = false; + + string sPath; + HRESULT hr = _GetString(pXmlNode, "Path", &sPath); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetPath(sPath); + } + + if (SUCCEEDED(hr)) + { + DWORD dwBlockSize; + hr = _GetDWORD(pXmlNode, "BlockSize", &dwBlockSize); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetBlockSizeInBytes(dwBlockSize); + } + } + + if (SUCCEEDED(hr)) + { + bool fInterlockedSequential; + hr = _GetBool(pXmlNode, "InterlockedSequential", &fInterlockedSequential); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetUseInterlockedSequential(fInterlockedSequential); + } + } + + if (SUCCEEDED(hr)) + { + UINT64 ullBaseFileOffset; + hr = _GetUINT64(pXmlNode, "BaseFileOffset", &ullBaseFileOffset); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetBaseFileOffsetInBytes(ullBaseFileOffset); + } + } + + if (SUCCEEDED(hr)) + { + bool fBool; + hr = _GetBool(pXmlNode, "SequentialScan", &fBool); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetSequentialScanHint(fBool); + } + } + + if (SUCCEEDED(hr)) + { + bool fBool; + hr = _GetBool(pXmlNode, "RandomAccess", &fBool); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetRandomAccessHint(fBool); + } + } + + if (SUCCEEDED(hr)) + { + bool fBool; + hr = _GetBool(pXmlNode, "TemporaryFile", &fBool); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetTemporaryFileHint(fBool); + } + } + + if (SUCCEEDED(hr)) + { + bool fUseLargePages; + hr = _GetBool(pXmlNode, "UseLargePages", &fUseLargePages); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetUseLargePages(fUseLargePages); + } + } + + if (SUCCEEDED(hr)) + { + DWORD dwRequestCount; + hr = _GetDWORD(pXmlNode, "RequestCount", &dwRequestCount); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetRequestCount(dwRequestCount); + } + } + + if (SUCCEEDED(hr)) + { + UINT64 ullStrideSize; + hr = _GetUINT64(pXmlNode, "StrideSize", &ullStrideSize); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetBlockAlignmentInBytes(ullStrideSize); + fSequential = true; + } + } + + if (SUCCEEDED(hr)) + { + UINT64 ullRandom; + hr = _GetUINT64(pXmlNode, "Random", &ullRandom); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + // conflict with sequential + if (fSequential) + { + fprintf(stderr, "sequential conflicts with \n"); + hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + else + { + pTarget->SetRandomRatio(100); + pTarget->SetBlockAlignmentInBytes(ullRandom); + } + } + } + + // now override default of 100% random? + if (SUCCEEDED(hr)) + { + UINT32 ulRandomRatio; + hr = _GetUINT32(pXmlNode, "RandomRatio", &ulRandomRatio); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + // conflict with sequential + if (fSequential) + { + fprintf(stderr, "sequential conflicts with \n"); + hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + else + { + pTarget->SetRandomRatio(ulRandomRatio); + } + } + } + + if (SUCCEEDED(hr)) + { + bool fBool; + hr = _GetBool(pXmlNode, "DisableOSCache", &fBool); + if (SUCCEEDED(hr) && (hr != S_FALSE) && fBool) + { + pTarget->SetCacheMode(TargetCacheMode::DisableOSCache); + } + } + + if (SUCCEEDED(hr)) + { + bool fBool; + hr = _GetBool(pXmlNode, "MemoryMappedIo", &fBool); + if (SUCCEEDED(hr) && (hr != S_FALSE) && fBool) + { + pTarget->SetMemoryMappedIoMode(MemoryMappedIoMode::On); + } + } + + if (SUCCEEDED(hr)) + { + bool fBool; + hr = _GetBool(pXmlNode, "DisableAllCache", &fBool); + if (SUCCEEDED(hr) && (hr != S_FALSE) && fBool) + { + pTarget->SetCacheMode(TargetCacheMode::DisableOSCache); + pTarget->SetWriteThroughMode(WriteThroughMode::On); + } + } + + if (SUCCEEDED(hr)) + { + bool fBool; + hr = _GetBool(pXmlNode, "DisableLocalCache", &fBool); + if (SUCCEEDED(hr) && (hr != S_FALSE) && fBool) + { + pTarget->SetCacheMode(TargetCacheMode::DisableLocalCache); + } + } + + if (SUCCEEDED(hr)) + { + bool fBool; + hr = _GetBool(pXmlNode, "WriteThrough", &fBool); + if (SUCCEEDED(hr) && (hr != S_FALSE) && fBool) + { + pTarget->SetWriteThroughMode(WriteThroughMode::On); + } + } + + if (SUCCEEDED(hr)) + { + string sFlushType; + hr = _GetString(pXmlNode, "FlushType", &sFlushType); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + if (sFlushType == "ViewOfFile") + { + pTarget->SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::ViewOfFile); + } + else if (sFlushType == "NonVolatileMemory") + { + pTarget->SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::NonVolatileMemory); + } + else if (sFlushType == "NonVolatileMemoryNoDrain") + { + pTarget->SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain); + } + else + { + hr = E_INVALIDARG; + } + } + } + + if (SUCCEEDED(hr)) + { + hr = _ParseWriteBufferContent(pXmlNode, pTarget); + } + + if (SUCCEEDED(hr)) + { + DWORD dwBurstSize; + hr = _GetDWORD(pXmlNode, "BurstSize", &dwBurstSize); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetBurstSize(dwBurstSize); + pTarget->SetUseBurstSize(true); + } + } + + if (SUCCEEDED(hr)) + { + DWORD dwThinkTime; + hr = _GetDWORD(pXmlNode, "ThinkTime", &dwThinkTime); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetThinkTime(dwThinkTime); + pTarget->SetEnableThinkTime(true); + } + } + + if (SUCCEEDED(hr)) + { + hr = _ParseThroughput(pXmlNode, pTarget); + } + + if (SUCCEEDED(hr)) + { + DWORD dwThreadsPerFile; + hr = _GetDWORD(pXmlNode, "ThreadsPerFile", &dwThreadsPerFile); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetThreadsPerFile(dwThreadsPerFile); + } + } + + if (SUCCEEDED(hr)) + { + UINT64 ullFileSize; + hr = _GetUINT64(pXmlNode, "FileSize", &ullFileSize); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetFileSize(ullFileSize); + pTarget->SetCreateFile(true); + } + } + + if (SUCCEEDED(hr)) + { + UINT64 ullMaxFileSize; + hr = _GetUINT64(pXmlNode, "MaxFileSize", &ullMaxFileSize); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetMaxFileSize(ullMaxFileSize); + } + } + + if (SUCCEEDED(hr)) + { + UINT32 ulWriteRatio; + hr = _GetUINT32(pXmlNode, "WriteRatio", &ulWriteRatio); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetWriteRatio(ulWriteRatio); + } + } + + if (SUCCEEDED(hr)) + { + bool fParallelAsyncIO; + hr = _GetBool(pXmlNode, "ParallelAsyncIO", &fParallelAsyncIO); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetUseParallelAsyncIO(fParallelAsyncIO); + } + } + + if (SUCCEEDED(hr)) + { + UINT64 ullThreadStride; + hr = _GetUINT64(pXmlNode, "ThreadStride", &ullThreadStride); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetThreadStrideInBytes(ullThreadStride); + } + } + + if (SUCCEEDED(hr)) + { + UINT32 ulIOPriority; + hr = _GetUINT32(pXmlNode, "IOPriority", &ulIOPriority); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + PRIORITY_HINT hint[] = { IoPriorityHintVeryLow, IoPriorityHintLow, IoPriorityHintNormal }; + pTarget->SetIOPriorityHint(hint[ulIOPriority - 1]); + } + } + + if (SUCCEEDED(hr)) + { + UINT32 ulWeight; + hr = _GetUINT32(pXmlNode, "Weight", &ulWeight); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetWeight(ulWeight); + } + } + + // + // Note: XSD validation ensures only one type of distribution is specified, but it as simple + // here to probe for each. + // + + if (SUCCEEDED(hr)) + { + hr = _ParseDistribution(pXmlNode, pTarget); + } + + if (SUCCEEDED(hr)) + { + hr = _ParseThreadTargets(pXmlNode, pTarget); + } + return hr; +} + +HRESULT XmlProfileParser::_ParseThroughput(IXMLDOMNode *pXmlNode, Target *pTarget) +{ + CComPtr spNode = nullptr; + CComVariant query("Throughput"); + HRESULT hr = pXmlNode->selectSingleNode(query.bstrVal, &spNode); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + // get value + UINT32 value = 0; + + BSTR bstrText; + hr = spNode->get_text(&bstrText); + if (SUCCEEDED(hr)) + { + value = (UINT32) _wtoi64((wchar_t *)bstrText); // XSD constrains s.t. cast is safe + SysFreeString(bstrText); + } + else + { + return hr; + } + + // get unit - bpms default + bool isBpms = true; + + CComPtr spNamedNodeMap = nullptr; + CComBSTR attr("unit"); + hr = spNode->get_attributes(&spNamedNodeMap); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + CComPtr spAttrNode = nullptr; + HRESULT hr = spNamedNodeMap->getNamedItem(attr, &spAttrNode); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + BSTR bstrText; + hr = spAttrNode->get_text(&bstrText); + if (SUCCEEDED(hr)) + { + isBpms = wcscmp((wchar_t *)bstrText, L"IOPS"); + SysFreeString(bstrText); + } + } + } + + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + if (isBpms) + { + pTarget->SetThroughput(value); + } + else + { + // NOTE: this depends on parse order s.t. blocksize is available + pTarget->SetThroughputIOPS(value); + } + } + } + return hr; +} + +HRESULT XmlProfileParser::_ParseThreadTargets(IXMLDOMNode *pXmlNode, Target *pTarget) +{ + CComVariant query("ThreadTargets/ThreadTarget"); + CComPtr spNodeList = nullptr; + HRESULT hr = pXmlNode->selectNodes(query.bstrVal, &spNodeList); + if (SUCCEEDED(hr)) + { + long cNodes; + hr = spNodeList->get_length(&cNodes); + if (SUCCEEDED(hr)) + { + for (int i = 0; i < cNodes; i++) + { + CComPtr spNode = nullptr; + hr = spNodeList->get_item(i, &spNode); + if (SUCCEEDED(hr)) + { + ThreadTarget threadTarget; + _ParseThreadTarget(spNode, &threadTarget); + pTarget->AddThreadTarget(threadTarget); + } + } + } + } + return hr; +} + +HRESULT XmlProfileParser::_ParseThreadTarget(IXMLDOMNode *pXmlNode, ThreadTarget *pThreadTarget) +{ + UINT32 ulThread; + HRESULT hr = _GetUINT32(pXmlNode, "Thread", &ulThread); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pThreadTarget->SetThread(ulThread); + } + + if (SUCCEEDED(hr)) + { + UINT32 ulWeight; + hr = _GetUINT32(pXmlNode, "Weight", &ulWeight); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pThreadTarget->SetWeight(ulWeight); + } + } + return hr; +} + +struct { + char* xPath; + DistributionType t; +} distributionTypes[] = { + { "Distribution/Absolute/Range", DistributionType::Absolute }, + { "Distribution/Percent/Range", DistributionType::Percent } +}; + +HRESULT XmlProfileParser::_ParseDistribution(IXMLDOMNode *pXmlNode, Target *pTarget) +{ + HRESULT hr = S_OK; + + for (auto& type : distributionTypes) + { + CComPtr spNodeList = nullptr; + CComVariant query(type.xPath); + hr = pXmlNode->selectNodes(query.bstrVal, &spNodeList); + if (SUCCEEDED(hr)) + { + long cNodes; + hr = spNodeList->get_length(&cNodes); + if (SUCCEEDED(hr) && cNodes != 0) + { + UINT64 targetBase = 0, targetSpan; + UINT32 ioBase = 0, ioSpan; + vector v; + + for (int i = 0; i < cNodes; i++) + { + // target span from the element + // note that this is the same 64bit int for both distribution types, + // it is the interpretation at the time the effective is calculated + // that makes the distinction. XSD covers range validations. + CComPtr spNode = nullptr; + hr = spNodeList->get_item(i, &spNode); + if (SUCCEEDED(hr)) + { + BSTR bstrText; + hr = spNode->get_text(&bstrText); + if (SUCCEEDED(hr)) + { + targetSpan = _wtoi64((wchar_t *)bstrText); + SysFreeString(bstrText); + } + } + + if (SUCCEEDED(hr)) + { + // io span from the attribute + CComPtr spNamedNodeMap = nullptr; + CComBSTR attr("IO"); + hr = spNode->get_attributes(&spNamedNodeMap); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + CComPtr spAttrNode = nullptr; + HRESULT hr = spNamedNodeMap->getNamedItem(attr, &spAttrNode); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + BSTR bstrText; + hr = spAttrNode->get_text(&bstrText); + if (SUCCEEDED(hr)) + { + ioSpan = _wtoi((wchar_t *)bstrText); + SysFreeString(bstrText); + } + } + } + } + + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + v.emplace_back(ioBase, ioSpan, + make_pair(targetBase, targetSpan)); + ioBase += ioSpan; + targetBase += targetSpan; + } + // failed during parse + else + { + break; + } + + // + // Note that we are aware here whether we got to 100% IO specification. + // This validation is delayed to the common path for XML/cmdline. + // + } + + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetDistributionRange(v, type.t); + } + + // if we parsed into the element, we are done (success or failure) - only one type is possible. + return hr; + } + } + } + + return hr; +} + +// Compatibility with the old, non-group aware affinity assignment. Preserved to allow downlevel XML profiles +// to run without modification. +// Any assignment done through this method will only assign within group 0, and is equivalent to the non-group +// specification -a#,#,# (contrast to -ag#,#,#,...). While not strictly equivalent to the old non-group aware +// behavior, this should be acceptably good-enough. +// +// The XML result parser no longer emits this form. + +HRESULT XmlProfileParser::_ParseAffinityAssignment(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan) +{ + CComPtr spNodeList = nullptr; + CComVariant query("Affinity/AffinityAssignment"); + HRESULT hr = pXmlNode->selectNodes(query.bstrVal, &spNodeList); + if (SUCCEEDED(hr)) + { + long cNodes; + hr = spNodeList->get_length(&cNodes); + if (SUCCEEDED(hr)) + { + for (int i = 0; i < cNodes; i++) + { + CComPtr spNode = nullptr; + hr = spNodeList->get_item(i, &spNode); + if (SUCCEEDED(hr)) + { + BSTR bstrText; + hr = spNode->get_text(&bstrText); + if (SUCCEEDED(hr)) + { + pTimeSpan->AddAffinityAssignment((WORD)0, (BYTE)_wtoi((wchar_t *)bstrText)); + SysFreeString(bstrText); + } + } + } + } + } + return hr; +} + +// Group aware affinity assignment. This is the only form emitted by the XML result parser. + +HRESULT XmlProfileParser::_ParseAffinityGroupAssignment(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan) +{ + CComPtr spNodeList = nullptr; + CComVariant query("Affinity/AffinityGroupAssignment"); + + HRESULT hr = pXmlNode->selectNodes(query.bstrVal, &spNodeList); + if (SUCCEEDED(hr)) + { + long cNodes; + hr = spNodeList->get_length(&cNodes); + if (SUCCEEDED(hr)) + { + for (int i = 0; i < cNodes; i++) + { + CComPtr spNode = nullptr; + hr = spNodeList->get_item(i, &spNode); + if (SUCCEEDED(hr)) + { + UINT32 dwGroup = 0, dwProc = 0; + hr = _GetUINT32Attr(spNode, "Group", &dwGroup); + if (SUCCEEDED(hr)) + { + _GetUINT32Attr(spNode, "Processor", &dwProc); + } + if (SUCCEEDED(hr)) + { + if (dwProc > MAXBYTE) + { + fprintf(stderr, "ERROR: profile specifies group assignment to core %u, out of range\n", dwProc); + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + } + if (dwGroup > MAXWORD) + { + fprintf(stderr, "ERROR: profile specifies group assignment group %u, out of range\n", dwGroup); + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + } + + if (SUCCEEDED(hr)) { + pTimeSpan->AddAffinityAssignment((WORD)dwGroup, (BYTE)dwProc); + } + + } + } + } + } + } + return hr; +} + +HRESULT XmlProfileParser::_GetUINT32(IXMLDOMNode *pXmlNode, const char *pszQuery, UINT32 *pulValue) const +{ + CComPtr spNode = nullptr; + CComVariant query(pszQuery); + HRESULT hr = pXmlNode->selectSingleNode(query.bstrVal, &spNode); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + BSTR bstrText; + hr = spNode->get_text(&bstrText); + if (SUCCEEDED(hr)) + { + *pulValue = _wtoi((wchar_t *)bstrText); // TODO: make sure it works on large unsigned ints + SysFreeString(bstrText); + } + } + return hr; +} + +HRESULT XmlProfileParser::_GetUINT32Attr(IXMLDOMNode *pXmlNode, const char *pszAttr, UINT32 *pulValue) const +{ + CComPtr spNamedNodeMap = nullptr; + CComBSTR attr(pszAttr); + HRESULT hr = pXmlNode->get_attributes(&spNamedNodeMap); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + CComPtr spNode = nullptr; + HRESULT hr = spNamedNodeMap->getNamedItem(attr, &spNode); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + BSTR bstrText; + hr = spNode->get_text(&bstrText); + if (SUCCEEDED(hr)) + { + *pulValue = _wtoi((wchar_t *)bstrText); // TODO: make sure it works on large unsigned ints + SysFreeString(bstrText); + } + } + } + return hr; +} + +HRESULT XmlProfileParser::_GetString(IXMLDOMNode *pXmlNode, const char *pszQuery, string *psValue) const +{ + CComPtr spNode = nullptr; + CComVariant query(pszQuery); + HRESULT hr = pXmlNode->selectSingleNode(query.bstrVal, &spNode); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + BSTR bstrText; + hr = spNode->get_text(&bstrText); + if (SUCCEEDED(hr)) + { + // TODO: use wstring? + char path[MAX_PATH] = {}; + WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, (wchar_t *)bstrText, static_cast(wcslen((wchar_t *)bstrText)), path, sizeof(path)-1, 0 /*lpDefaultChar*/, 0 /*lpUsedDefaultChar*/); + *psValue = string(path); + } + SysFreeString(bstrText); + } + return hr; +} + +HRESULT XmlProfileParser::_GetUINT64(IXMLDOMNode *pXmlNode, const char *pszQuery, UINT64 *pullValue) const +{ + CComPtr spNode = nullptr; + CComVariant query(pszQuery); + HRESULT hr = pXmlNode->selectSingleNode(query.bstrVal, &spNode); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + BSTR bstrText; + hr = spNode->get_text(&bstrText); + if (SUCCEEDED(hr)) + { + *pullValue = _wtoi64((wchar_t *)bstrText); + } + SysFreeString(bstrText); + } + return hr; +} + +HRESULT XmlProfileParser::_GetDWORD(IXMLDOMNode *pXmlNode, const char *pszQuery, DWORD *pdwValue) const +{ + UINT32 value = 0; + HRESULT hr = _GetUINT32(pXmlNode, pszQuery, &value); + if (SUCCEEDED(hr)) + { + *pdwValue = value; + } + return hr; +} + +HRESULT XmlProfileParser::_GetBool(IXMLDOMNode *pXmlNode, const char *pszQuery, bool *pfValue) const +{ + HRESULT hr = S_OK; + CComPtr spNode = nullptr; + CComVariant query(pszQuery); + hr = pXmlNode->selectSingleNode(query.bstrVal, &spNode); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + BSTR bstrText; + hr = spNode->get_text(&bstrText); + if (SUCCEEDED(hr)) + { + *pfValue = (_wcsicmp(L"true", (wchar_t *)bstrText) == 0); + SysFreeString(bstrText); + } + } + return hr; +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/XmlProfileParser/diskspd.xsd b/CristalDiskMark/source/diskspd22/XmlProfileParser/diskspd.xsd new file mode 100644 index 0000000..f32eab3 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/XmlProfileParser/diskspd.xsd @@ -0,0 +1,400 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CristalDiskMark/source/diskspd22/XmlResultParser/XmlResultParser.cpp b/CristalDiskMark/source/diskspd22/XmlResultParser/XmlResultParser.cpp new file mode 100644 index 0000000..aeb0042 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/XmlResultParser/XmlResultParser.cpp @@ -0,0 +1,620 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "xmlresultparser.h" + +// TODO: refactor to a single function shared with the ResultParser +char printBuffer[4096] = {}; + +/// for CrystalDiskMark +int XmlResultParser::GetTotalScore() +{ + return 0; +} + +double XmlResultParser::GetAverageLatency() +{ + return 0.0; +} + +void XmlResultParser::_PrintV(const char *format, va_list listArg) +{ + _sResult.append(_indent, ' '); + vsprintf_s(printBuffer, _countof(printBuffer), format, listArg); + _sResult += printBuffer; +} + +void XmlResultParser::_Print(const char *format, ...) +{ + assert(nullptr != format); + va_list listArg; + va_start(listArg, format); + + _PrintV(format, listArg); + va_end(listArg); +} + +void XmlResultParser::_PrintInc(const char *format, ...) +{ + assert(nullptr != format); + va_list listArg; + va_start(listArg, format); + + // Print & Increment Indent + // e.g., + + _PrintV(format, listArg); + _indent += 2; + va_end(listArg); +} + +void XmlResultParser::_PrintDec(const char *format, ...) +{ + assert(nullptr != format); + va_list listArg; + va_start(listArg, format); + + // Decrement Indent & Print + // e.g., + + _indent -= 2; + _PrintV(format, listArg); + va_end(listArg); +} + +void XmlResultParser::_PrintTargetResults(const TargetResults& results) +{ + // TODO: results.readBucketizer; + // TODO: results.writeBucketizer; + + _Print("%s\n", results.sPath.c_str()); + _Print("%I64u\n", results.ullBytesCount); + _Print("%I64u\n", results.ullFileSize); + _Print("%I64u\n", results.ullIOCount); + _Print("%I64u\n", results.ullReadBytesCount); + _Print("%I64u\n", results.ullReadIOCount); + _Print("%I64u\n", results.ullWriteBytesCount); + _Print("%I64u\n", results.ullWriteIOCount); + + if (results.vDistributionRange.size()) + { + _PrintInc("\n"); + _PrintInc("\n"); + + // + // Render hole(s) in effective distribution. Keep track of the expected base + // of the next range and render a hole (IO = 0) over the gap as needed. + // + + UINT64 expectBase = 0; + for (auto& r : results.vDistributionRange) + { + if (r._dst.first != expectBase) + { + _Print("%I64u\n", 0, r._dst.first - expectBase); + } + + _Print("%I64u\n", r._span, r._dst.second); + expectBase = r._dst.first + r._dst.second; + } + + _PrintDec("\n"); + _PrintDec("\n"); + } +} + +void XmlResultParser::_PrintTargetLatency(const TargetResults& results) +{ + if (results.readLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", results.readLatencyHistogram.GetAvg() / 1000); + _Print("%.3f\n", results.readLatencyHistogram.GetStandardDeviation() / 1000); + } + if (results.writeLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", results.writeLatencyHistogram.GetAvg() / 1000); + _Print("%.3f\n", results.writeLatencyHistogram.GetStandardDeviation() / 1000); + } + Histogram totalLatencyHistogram; + totalLatencyHistogram.Merge(results.readLatencyHistogram); + totalLatencyHistogram.Merge(results.writeLatencyHistogram); + if (totalLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", totalLatencyHistogram.GetAvg() / 1000); + _Print("%.3f\n", totalLatencyHistogram.GetStandardDeviation() / 1000); + } +} + +void XmlResultParser::_PrintTargetIops(const IoBucketizer& readBucketizer, const IoBucketizer& writeBucketizer, UINT32 bucketTimeInMs) +{ + _PrintInc("\n"); + + IoBucketizer totalIoBucketizer; + totalIoBucketizer.Merge(readBucketizer); + totalIoBucketizer.Merge(writeBucketizer); + + if (readBucketizer.GetNumberOfValidBuckets() > 0) + { + _Print("%.3f\n", readBucketizer.GetStandardDeviationIOPS() / (bucketTimeInMs / 1000.0)); + } + if (writeBucketizer.GetNumberOfValidBuckets() > 0) + { + _Print("%.3f\n", writeBucketizer.GetStandardDeviationIOPS() / (bucketTimeInMs / 1000.0)); + } + if (totalIoBucketizer.GetNumberOfValidBuckets() > 0) + { + _Print("%.3f\n", totalIoBucketizer.GetStandardDeviationIOPS() / (bucketTimeInMs / 1000.0)); + } + _PrintIops(readBucketizer, writeBucketizer, bucketTimeInMs); + _PrintDec("\n"); +} + +void XmlResultParser::_PrintETWSessionInfo(struct ETWSessionInfo sessionInfo) +{ + _PrintInc("\n"); + _Print("%lu\n", sessionInfo.ulBufferSize); + _Print("%lu\n", sessionInfo.ulMinimumBuffers); + _Print("%lu\n", sessionInfo.ulMaximumBuffers); + _Print("%lu", sessionInfo.ulFreeBuffers); + _Print("%lu\n", sessionInfo.ulBuffersWritten); + _Print("%lu\n", sessionInfo.ulFlushTimer); + _Print("%d\n", sessionInfo.lAgeLimit); + + _Print("%lu\n", sessionInfo.ulNumberOfBuffers); + _Print("%lu\n", sessionInfo.ulEventsLost); + _Print("%lu\n", sessionInfo.ulLogBuffersLost); + _Print("%lu\n", sessionInfo.ulRealTimeBuffersLost); + _PrintDec("\n"); +} + +void XmlResultParser::_PrintETW(struct ETWMask ETWMask, struct ETWEventCounters EtwEventCounters) +{ + _PrintInc("\n"); + if (ETWMask.bDiskIO) + { + _PrintInc("\n"); + _Print("%I64u\n", EtwEventCounters.ullIORead); + _Print("%I64u\n", EtwEventCounters.ullIOWrite); + _PrintDec("\n"); + } + if (ETWMask.bImageLoad) + { + _Print("%I64u\n", EtwEventCounters.ullImageLoad); + } + if (ETWMask.bMemoryPageFaults) + { + _PrintInc("\n"); + _Print("%I64u\n", EtwEventCounters.ullMMCopyOnWrite); + _Print("%I64u\n", EtwEventCounters.ullMMDemandZeroFault); + _Print("%I64u\n", EtwEventCounters.ullMMGuardPageFault); + _Print("%I64u\n", EtwEventCounters.ullMMHardPageFault); + _Print("%I64u\n", EtwEventCounters.ullMMTransitionFault); + _PrintDec("\n"); + } + if (ETWMask.bMemoryHardFaults && !ETWMask.bMemoryPageFaults) + { + _Print("%I64u\n", EtwEventCounters.ullMMHardPageFault); + } + if (ETWMask.bNetwork) + { + _PrintInc("\n"); + _Print("%I64u\n", EtwEventCounters.ullNetAccept); + _Print("%I64u\n", EtwEventCounters.ullNetConnect); + _Print("%I64u\n", EtwEventCounters.ullNetDisconnect); + _Print("%I64u\n", EtwEventCounters.ullNetReconnect); + _Print("%I64u\n", EtwEventCounters.ullNetRetransmit); + _Print("%I64u\n", EtwEventCounters.ullNetTcpSend); + _Print("%I64u\n", EtwEventCounters.ullNetTcpReceive); + _Print("%I64u\n", EtwEventCounters.ullNetUdpSend); + _Print("%I64u\n", EtwEventCounters.ullNetUdpReceive); + _PrintDec("\n"); + } + if (ETWMask.bProcess) + { + _PrintInc("\n"); + _Print("%I64u\n", EtwEventCounters.ullProcessStart); + _Print("%I64u\n", EtwEventCounters.ullProcessEnd); + _PrintDec("\n"); + } + if (ETWMask.bRegistry) + { + _PrintInc("\n"); + _Print("%I64u\n", EtwEventCounters.ullRegCreate); + _Print("%I64u\n", EtwEventCounters.ullRegDelete); + _Print("%I64u\n", EtwEventCounters.ullRegDeleteValue); + _Print("%I64u\n", EtwEventCounters.ullRegEnumerateKey); + _Print("%I64u\n", EtwEventCounters.ullRegEnumerateValueKey); + _Print("%I64u\n", EtwEventCounters.ullRegFlush); + _Print("%I64u\n", EtwEventCounters.ullRegOpen); + _Print("%I64u\n", EtwEventCounters.ullRegQuery); + _Print("%I64u\n", EtwEventCounters.ullRegQueryMultipleValue); + _Print("%I64u\n", EtwEventCounters.ullRegQueryValue); + _Print("%I64u\n", EtwEventCounters.ullRegSetInformation); + _Print("%I64u\n", EtwEventCounters.ullRegSetValue); + _PrintDec("\n"); + } + if (ETWMask.bThread) + { + _PrintInc("\n"); + _Print("%I64u\n", EtwEventCounters.ullThreadStart); + _Print("%I64u\n", EtwEventCounters.ullThreadEnd); + _PrintDec("\n"); + } + _PrintDec("\n"); +} + +void XmlResultParser::_PrintCpuUtilization(const Results& results, const SystemInformation& system) +{ + const auto& topo = system.processorTopology; + size_t procCount = results.vSystemProcessorPerfInfo.size(); + size_t baseProc = 0; + BYTE efficiencyClass = 0; + BYTE processorCore = 0; + + _PrintInc("\n"); + + double busyTime = 0; + double totalIdleTime = 0; + double totalUserTime = 0; + double totalKrnlTime = 0; + + for (const auto& group : topo._vProcessorGroupInformation) { + + // Sanity assert - results are sized to the sum of active processors + assert(baseProc + group._activeProcessorCount <= procCount); + + for (BYTE processor = 0; processor < group._activeProcessorCount; processor++) { + + long long fTime = results.vSystemProcessorPerfInfo[baseProc + processor].KernelTime.QuadPart + + results.vSystemProcessorPerfInfo[baseProc + processor].UserTime.QuadPart; + + double idleTime = 100.0 * results.vSystemProcessorPerfInfo[baseProc + processor].IdleTime.QuadPart / fTime; + double krnlTime = 100.0 * results.vSystemProcessorPerfInfo[baseProc + processor].KernelTime.QuadPart / fTime; + double userTime = 100.0 * results.vSystemProcessorPerfInfo[baseProc + processor].UserTime.QuadPart / fTime; + double usedTime = (krnlTime - idleTime) + userTime; + + _PrintInc("\n"); + _Print("%d\n", topo.GetSocketOfProcessor(group._groupNumber, processor)); + _Print("%d\n", topo.GetNumaOfProcessor(group._groupNumber, processor)); + _Print("%d\n", group._groupNumber); + processorCore = topo.GetCoreOfProcessor(group._groupNumber, processor, efficiencyClass); + _Print("%d\n", processorCore); + _Print("%d\n", efficiencyClass); + _Print("%d\n", processor); + _Print("%.2f\n", usedTime); + _Print("%.2f\n", userTime); + _Print("%.2f\n", krnlTime - idleTime); + _Print("%.2f\n", idleTime); + _PrintDec("\n"); + + busyTime += usedTime; + totalIdleTime += idleTime; + totalUserTime += userTime; + totalKrnlTime += krnlTime; + } + + baseProc += group._activeProcessorCount; + } + + assert(baseProc == procCount); + + _PrintInc("\n"); + _Print("%.2f\n", busyTime / procCount); + _Print("%.2f\n", totalUserTime / procCount); + _Print("%.2f\n", (totalKrnlTime - totalIdleTime) / procCount); + _Print("%.2f\n", totalIdleTime / procCount); + _PrintDec("\n"); + + _PrintDec("\n"); +} + +// emit the iops time series (this obviates needing perfmon counters, in common cases, and provides file level data) +void XmlResultParser::_PrintIops(const IoBucketizer& readBucketizer, const IoBucketizer& writeBucketizer, UINT32 bucketTimeInMs) +{ + bool done = false; + for (size_t i = 0; !done; i++) + { + done = true; + + double r = 0.0; + double r_min = 0.0; + double r_max = 0.0; + double r_avg = 0.0; + double r_stddev = 0.0; + + double w = 0.0; + double w_min = 0.0; + double w_max = 0.0; + double w_avg = 0.0; + double w_stddev = 0.0; + + if (readBucketizer.GetNumberOfValidBuckets() > i) + { + r = readBucketizer.GetIoBucketCount(i) / (bucketTimeInMs / 1000.0); + r_min = readBucketizer.GetIoBucketMinDurationUsec(i) / 1000.0; + r_max = readBucketizer.GetIoBucketMaxDurationUsec(i) / 1000.0; + r_avg = readBucketizer.GetIoBucketAvgDurationUsec(i) / 1000.0; + r_stddev = readBucketizer.GetIoBucketDurationStdDevUsec(i) / 1000.0; + done = false; + } + if (writeBucketizer.GetNumberOfValidBuckets() > i) + { + w = writeBucketizer.GetIoBucketCount(i) / (bucketTimeInMs / 1000.0); + w_min = writeBucketizer.GetIoBucketMinDurationUsec(i) / 1000.0; + w_max = writeBucketizer.GetIoBucketMaxDurationUsec(i) / 1000.0; + w_avg = writeBucketizer.GetIoBucketAvgDurationUsec(i) / 1000.0; + w_stddev = writeBucketizer.GetIoBucketDurationStdDevUsec(i) / 1000.0; + done = false; + } + if (!done) + { + _Print("\n", + bucketTimeInMs*(i + 1), r, w, r + w, + r_min, r_max, r_avg, r_stddev, + w_min, w_max, w_avg, w_stddev); + } + } +} + +void XmlResultParser::_PrintOverallIops(const Results& results, UINT32 bucketTimeInMs) +{ + IoBucketizer readBucketizer; + IoBucketizer writeBucketizer; + + for (const auto& thread : results.vThreadResults) + { + for (const auto& target : thread.vTargetResults) + { + readBucketizer.Merge(target.readBucketizer); + writeBucketizer.Merge(target.writeBucketizer); + } + } + + _PrintTargetIops(readBucketizer, writeBucketizer, bucketTimeInMs); +} + +void XmlResultParser::_PrintLatencyPercentiles(const Results& results) +{ + Histogram readLatencyHistogram; + Histogram writeLatencyHistogram; + Histogram totalLatencyHistogram; + + for (const auto& thread : results.vThreadResults) + { + for (const auto& target : thread.vTargetResults) + { + readLatencyHistogram.Merge(target.readLatencyHistogram); + + writeLatencyHistogram.Merge(target.writeLatencyHistogram); + + totalLatencyHistogram.Merge(target.writeLatencyHistogram); + totalLatencyHistogram.Merge(target.readLatencyHistogram); + } + } + + _PrintInc("\n", + readLatencyHistogram.GetSampleBuckets(), + writeLatencyHistogram.GetSampleBuckets(), + totalLatencyHistogram.GetSampleBuckets()); + + if (readLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", readLatencyHistogram.GetAvg() / 1000); + _Print("%.3f\n", readLatencyHistogram.GetStandardDeviation() / 1000); + } + if (writeLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", writeLatencyHistogram.GetAvg() / 1000); + _Print("%.3f\n", writeLatencyHistogram.GetStandardDeviation() / 1000); + } + if (totalLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", totalLatencyHistogram.GetAvg() / 1000); + _Print("%.3f\n", totalLatencyHistogram.GetStandardDeviation() / 1000); + } + + _PrintInc("\n"); + _Print("0\n"); + if (readLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", readLatencyHistogram.GetMin() / 1000); + } + if (writeLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", writeLatencyHistogram.GetMin() / 1000); + } + if (totalLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", totalLatencyHistogram.GetMin() / 1000); + } + _PrintDec("\n"); + + // Construct vector of percentiles and decimal precision to squelch trailing zeroes. This is more + // detailed than summary text output, and does not contain the decorated names (15th, etc.) + + vector> vPercentiles; + for (int p = 1; p <= 99; p++) + { + vPercentiles.push_back(make_pair(0, p)); + } + + vPercentiles.push_back(make_pair(1, 99.9)); + vPercentiles.push_back(make_pair(2, 99.99)); + vPercentiles.push_back(make_pair(3, 99.999)); + vPercentiles.push_back(make_pair(4, 99.9999)); + vPercentiles.push_back(make_pair(5, 99.99999)); + vPercentiles.push_back(make_pair(6, 99.999999)); + vPercentiles.push_back(make_pair(7, 99.9999999)); + + for (auto p : vPercentiles) + { + _PrintInc("\n"); + _Print("%.*f\n", p.first, p.second); + if (readLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", readLatencyHistogram.GetPercentile(p.second / 100) / 1000); + } + if (writeLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", writeLatencyHistogram.GetPercentile(p.second / 100) / 1000); + } + if (totalLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", totalLatencyHistogram.GetPercentile(p.second / 100) / 1000); + } + _PrintDec("\n"); + } + + _PrintInc("\n"); + _Print("100\n"); + if (readLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", readLatencyHistogram.GetMax() / 1000); + } + if (writeLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", writeLatencyHistogram.GetMax() / 1000); + } + if (totalLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", totalLatencyHistogram.GetMax() / 1000); + } + _PrintDec("\n"); + _PrintDec("\n"); +} + +string XmlResultParser::ParseProfile(const Profile& profile) +{ + _sResult = profile.GetXml(0); + return _sResult; +} + +void XmlResultParser::_PrintWaitStats(const ThreadResults &threadResult) +{ + _PrintInc("\n"); + _Print("%llu\n", threadResult.WaitStats.Wait); + _Print("%llu\n", threadResult.WaitStats.ThrottleWait); + _Print("%llu\n", threadResult.WaitStats.ThrottleSleep); + _Print("%llu\n", threadResult.WaitStats.Lookaside); + _Print("%llu %llu %llu %llu %llu %llu %llu %llu\n", + threadResult.WaitStats.LookasideCompletion[0], + threadResult.WaitStats.LookasideCompletion[1], + threadResult.WaitStats.LookasideCompletion[2], + threadResult.WaitStats.LookasideCompletion[3], + threadResult.WaitStats.LookasideCompletion[4], + threadResult.WaitStats.LookasideCompletion[5], + threadResult.WaitStats.LookasideCompletion[6], + threadResult.WaitStats.LookasideCompletion[7]); + _PrintDec("\n"); +} + +string XmlResultParser::ParseResults(const Profile& profile, const SystemInformation& system, vector vResults) +{ + _sResult.clear(); + + _PrintInc("\n"); + + _sResult += system.GetXml(_indent); + _sResult += profile.GetXml(_indent); + for (size_t iResults = 0; iResults < vResults.size(); iResults++) + { + const Results& results = vResults[iResults]; + const TimeSpan& timeSpan = profile.GetTimeSpans()[iResults]; + + _PrintInc("\n"); + + double fTime = PerfTimer::PerfTimeToSeconds(results.ullTimeCount); //test duration + if (fTime >= 0.0000001) + { + // There either is a fixed number of threads for all files to share (GetThreadCount() > 0) or a number of threads per file. + // In the latter case vThreadResults.size() == number of threads per file * file count + size_t ulThreadCnt = (timeSpan.GetThreadCount() > 0) ? timeSpan.GetThreadCount() : results.vThreadResults.size(); + + _Print("%.2f\n", fTime); + _Print("%u\n", ulThreadCnt); + _Print("%u\n", timeSpan.GetRequestCount()); + _Print("%u\n", system.processorTopology._ulProcessorCount); + + _PrintCpuUtilization(results, system); + + if (timeSpan.GetMeasureLatency()) + { + _PrintLatencyPercentiles(results); + } + + if (timeSpan.GetCalculateIopsStdDev()) + { + _PrintOverallIops(results, timeSpan.GetIoBucketDurationInMilliseconds()); + } + + if (results.fUseETW) + { + _PrintETW(results.EtwMask, results.EtwEventCounters); + _PrintETWSessionInfo(results.EtwSessionInfo); + } + + for (size_t iThread = 0; iThread < results.vThreadResults.size(); iThread++) + { + const ThreadResults& threadResults = results.vThreadResults[iThread]; + _PrintInc("\n"); + _Print("%u\n", iThread); + for (const auto& targetResults : threadResults.vTargetResults) + { + _PrintInc("\n"); + _PrintTargetResults(targetResults); + if (timeSpan.GetMeasureLatency()) + { + _PrintTargetLatency(targetResults); + } + if (timeSpan.GetCalculateIopsStdDev()) + { + _PrintTargetIops(targetResults.readBucketizer, targetResults.writeBucketizer, timeSpan.GetIoBucketDurationInMilliseconds()); + } + _PrintDec("\n"); + } + if (profile.GetVerboseStats()) + { + _PrintWaitStats(threadResults); + } + _PrintDec("\n"); + } + } + else + { + _Print("The test was interrupted before the measurements began. No results are displayed.\n"); + } + + _PrintDec("\n"); + } + + _PrintDec(""); + return _sResult; +} diff --git a/CristalDiskMark/source/diskspd22/diskspd.wprp b/CristalDiskMark/source/diskspd22/diskspd.wprp new file mode 100644 index 0000000..5f01e79 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/diskspd.wprp @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CristalDiskMark/source/diskspd22/diskspd_vs/CmdLineParser/CmdLineParser.vcxproj b/CristalDiskMark/source/diskspd22/diskspd_vs/CmdLineParser/CmdLineParser.vcxproj new file mode 100644 index 0000000..bfab1a4 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/diskspd_vs/CmdLineParser/CmdLineParser.vcxproj @@ -0,0 +1,272 @@ + + + + + Debug + ARM + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {0EF5CE78-8E92-4A1B-A255-0F544AADA291} + CmdLineParser + 10.0 + + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + MaxSpeed + true + true + true + true + _MBCS;NDEBUG;%(PreprocessorDefinitions) + MultiThreaded + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + _MBCS;NDEBUG;%(PreprocessorDefinitions) + MultiThreaded + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + _MBCS;NDEBUG;_ARM64_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions) + MultiThreaded + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + _MBCS;NDEBUG;_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions) + MultiThreaded + + + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/diskspd_vs/CmdRequestCreator/CmdRequestCreator.vcxproj b/CristalDiskMark/source/diskspd22/diskspd_vs/CmdRequestCreator/CmdRequestCreator.vcxproj new file mode 100644 index 0000000..359f3a7 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/diskspd_vs/CmdRequestCreator/CmdRequestCreator.vcxproj @@ -0,0 +1,300 @@ + + + + + Debug + ARM + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0} + CmdRequestCreator + 10.0 + + + + Application + true + MultiByte + v143 + + + Application + true + MultiByte + v143 + + + Application + true + MultiByte + v143 + + + Application + true + MultiByte + v143 + + + Application + false + true + MultiByte + v143 + + + Application + false + true + MultiByte + v143 + + + Application + false + true + MultiByte + v143 + + + Application + false + true + MultiByte + v143 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)\..\Common;$(IncludePath) + diskspd + + + $(SolutionDir)\..\Common;$(IncludePath) + diskspd + false + + + $(SolutionDir)\..\Common;$(IncludePath) + diskspd + false + + + $(SolutionDir)\..\Common;$(IncludePath) + diskspd + false + + + $(SolutionDir)\..\Common;$(IncludePath) + DiskSpd32 + + + $(SolutionDir)\..\Common;$(IncludePath) + DiskSpd64 + + + $(SolutionDir)\..\Common;$(IncludePath) + DiskSpdA64 + + + $(SolutionDir)\..\Common;$(IncludePath) + DiskSpdA32 + + + + Level3 + Disabled + true + false + true + + + true + $(SolutionDir)$(Configuration)\xmlprofileparser.lib;$(SolutionDir)$(Configuration)\iorequestgenerator.lib;$(SolutionDir)$(Configuration)\resultparser.lib;$(SolutionDir)$(Configuration)\xmlresultparser.lib;$(SolutionDir)$(Configuration)\common.lib;powrprof.lib;msxml6.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + true + false + true + + + true + $(SolutionDir)$(Platform)\$(Configuration)\xmlprofileparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\iorequestgenerator.lib;$(SolutionDir)$(Platform)\$(Configuration)\resultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\xmlresultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\common.lib;powrprof.lib;msxml6.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + true + false + true + + + true + $(SolutionDir)$(Platform)\$(Configuration)\xmlprofileparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\iorequestgenerator.lib;$(SolutionDir)$(Platform)\$(Configuration)\resultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\xmlresultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\common.lib;powrprof.lib;msxml6.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + true + false + true + + + true + $(SolutionDir)$(Platform)\$(Configuration)\xmlprofileparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\iorequestgenerator.lib;$(SolutionDir)$(Platform)\$(Configuration)\resultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\xmlresultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\common.lib;powrprof.lib;msxml6.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + true + true + true + $(SolutionDir)$(Configuration)\xmlprofileparser.lib;$(SolutionDir)$(Configuration)\iorequestgenerator.lib;$(SolutionDir)$(Configuration)\resultparser.lib;$(SolutionDir)$(Configuration)\xmlresultparser.lib;$(SolutionDir)$(Configuration)\common.lib;powrprof.lib;msxml6.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + true + true + true + $(SolutionDir)$(Platform)\$(Configuration)\xmlprofileparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\iorequestgenerator.lib;$(SolutionDir)$(Platform)\$(Configuration)\resultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\xmlresultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\common.lib;powrprof.lib;msxml6.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + true + true + true + $(SolutionDir)$(Platform)\$(Configuration)\xmlprofileparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\iorequestgenerator.lib;$(SolutionDir)$(Platform)\$(Configuration)\resultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\xmlresultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\common.lib;powrprof.lib;msxml6.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + true + true + true + $(SolutionDir)$(Platform)\$(Configuration)\xmlprofileparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\iorequestgenerator.lib;$(SolutionDir)$(Platform)\$(Configuration)\resultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\xmlresultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\common.lib;powrprof.lib;msxml6.lib;%(AdditionalDependencies) + + + + + {0ef5ce78-8e92-4a1b-a255-0f544aada291} + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/diskspd_vs/Common/Common.vcxproj b/CristalDiskMark/source/diskspd22/diskspd_vs/Common/Common.vcxproj new file mode 100644 index 0000000..0b9fa4f --- /dev/null +++ b/CristalDiskMark/source/diskspd22/diskspd_vs/Common/Common.vcxproj @@ -0,0 +1,248 @@ + + + + + Debug + ARM + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {B253AB42-F482-417A-82CE-EDAFCD26F366} + Common + 10.0 + + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + MaxSpeed + true + true + true + true + MultiThreaded + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + MultiThreaded + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + MultiThreaded + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + MultiThreaded + + + true + true + true + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/diskspd_vs/IORequestGenerator/IORequestGenerator.vcxproj b/CristalDiskMark/source/diskspd22/diskspd_vs/IORequestGenerator/IORequestGenerator.vcxproj new file mode 100644 index 0000000..9f6d9bb --- /dev/null +++ b/CristalDiskMark/source/diskspd22/diskspd_vs/IORequestGenerator/IORequestGenerator.vcxproj @@ -0,0 +1,278 @@ + + + + + Debug + ARM + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {62DB1E99-FBA0-45FD-9355-423059BA03B8} + IORequestGenerator + 10.0 + + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + true + true + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/diskspd_vs/ResultParser/ResultParser.vcxproj b/CristalDiskMark/source/diskspd22/diskspd_vs/ResultParser/ResultParser.vcxproj new file mode 100644 index 0000000..dbe59a0 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/diskspd_vs/ResultParser/ResultParser.vcxproj @@ -0,0 +1,272 @@ + + + + + Debug + ARM + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {F6C211DC-B076-4716-BCDC-D7DE88973B66} + ResultParser + 10.0 + + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests.sln b/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests.sln new file mode 100644 index 0000000..3c1cb90 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests.sln @@ -0,0 +1,81 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2010 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CmdLineParser", "UnitTests\CmdLineParser\CmdLineParser.vcxproj", "{54186266-8BA1-438C-AE76-AD64503CA6E9}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "UnitTests\Common\Common.vcxproj", "{BA9F561C-B103-48C9-A7C8-CE2B6BD89511}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IORequestGenerator", "UnitTests\IORequestGenerator\IORequestGenerator.vcxproj", "{13683A8B-2641-4287-9D66-A87834885057}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "XmlProfileParser", "UnitTests\XmlProfileParser\XmlProfileParser.vcxproj", "{B20AA8CF-ADFB-487C-B8F9-DBD3037F53AD}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "XmlResultParser", "UnitTests\XmlResultParser\XmlResultParser.vcxproj", "{D52F964B-C636-4DCC-AA7E-EE83A7AC41AD}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ResultParser", "UnitTests\ResultParser\ResultParser.vcxproj", "{471E64C7-2C65-4E16-A82D-4BF22AE690DD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {54186266-8BA1-438C-AE76-AD64503CA6E9}.Debug|x64.ActiveCfg = Debug|x64 + {54186266-8BA1-438C-AE76-AD64503CA6E9}.Debug|x64.Build.0 = Debug|x64 + {54186266-8BA1-438C-AE76-AD64503CA6E9}.Debug|x86.ActiveCfg = Debug|Win32 + {54186266-8BA1-438C-AE76-AD64503CA6E9}.Debug|x86.Build.0 = Debug|Win32 + {54186266-8BA1-438C-AE76-AD64503CA6E9}.Release|x64.ActiveCfg = Release|x64 + {54186266-8BA1-438C-AE76-AD64503CA6E9}.Release|x64.Build.0 = Release|x64 + {54186266-8BA1-438C-AE76-AD64503CA6E9}.Release|x86.ActiveCfg = Release|Win32 + {54186266-8BA1-438C-AE76-AD64503CA6E9}.Release|x86.Build.0 = Release|Win32 + {BA9F561C-B103-48C9-A7C8-CE2B6BD89511}.Debug|x64.ActiveCfg = Debug|x64 + {BA9F561C-B103-48C9-A7C8-CE2B6BD89511}.Debug|x64.Build.0 = Debug|x64 + {BA9F561C-B103-48C9-A7C8-CE2B6BD89511}.Debug|x86.ActiveCfg = Debug|Win32 + {BA9F561C-B103-48C9-A7C8-CE2B6BD89511}.Debug|x86.Build.0 = Debug|Win32 + {BA9F561C-B103-48C9-A7C8-CE2B6BD89511}.Release|x64.ActiveCfg = Release|x64 + {BA9F561C-B103-48C9-A7C8-CE2B6BD89511}.Release|x64.Build.0 = Release|x64 + {BA9F561C-B103-48C9-A7C8-CE2B6BD89511}.Release|x86.ActiveCfg = Release|Win32 + {BA9F561C-B103-48C9-A7C8-CE2B6BD89511}.Release|x86.Build.0 = Release|Win32 + {13683A8B-2641-4287-9D66-A87834885057}.Debug|x64.ActiveCfg = Debug|x64 + {13683A8B-2641-4287-9D66-A87834885057}.Debug|x64.Build.0 = Debug|x64 + {13683A8B-2641-4287-9D66-A87834885057}.Debug|x86.ActiveCfg = Debug|Win32 + {13683A8B-2641-4287-9D66-A87834885057}.Debug|x86.Build.0 = Debug|Win32 + {13683A8B-2641-4287-9D66-A87834885057}.Release|x64.ActiveCfg = Release|x64 + {13683A8B-2641-4287-9D66-A87834885057}.Release|x64.Build.0 = Release|x64 + {13683A8B-2641-4287-9D66-A87834885057}.Release|x86.ActiveCfg = Release|Win32 + {13683A8B-2641-4287-9D66-A87834885057}.Release|x86.Build.0 = Release|Win32 + {B20AA8CF-ADFB-487C-B8F9-DBD3037F53AD}.Debug|x64.ActiveCfg = Debug|x64 + {B20AA8CF-ADFB-487C-B8F9-DBD3037F53AD}.Debug|x64.Build.0 = Debug|x64 + {B20AA8CF-ADFB-487C-B8F9-DBD3037F53AD}.Debug|x86.ActiveCfg = Debug|Win32 + {B20AA8CF-ADFB-487C-B8F9-DBD3037F53AD}.Debug|x86.Build.0 = Debug|Win32 + {B20AA8CF-ADFB-487C-B8F9-DBD3037F53AD}.Release|x64.ActiveCfg = Release|x64 + {B20AA8CF-ADFB-487C-B8F9-DBD3037F53AD}.Release|x64.Build.0 = Release|x64 + {B20AA8CF-ADFB-487C-B8F9-DBD3037F53AD}.Release|x86.ActiveCfg = Release|Win32 + {B20AA8CF-ADFB-487C-B8F9-DBD3037F53AD}.Release|x86.Build.0 = Release|Win32 + {D52F964B-C636-4DCC-AA7E-EE83A7AC41AD}.Debug|x64.ActiveCfg = Debug|x64 + {D52F964B-C636-4DCC-AA7E-EE83A7AC41AD}.Debug|x64.Build.0 = Debug|x64 + {D52F964B-C636-4DCC-AA7E-EE83A7AC41AD}.Debug|x86.ActiveCfg = Debug|Win32 + {D52F964B-C636-4DCC-AA7E-EE83A7AC41AD}.Debug|x86.Build.0 = Debug|Win32 + {D52F964B-C636-4DCC-AA7E-EE83A7AC41AD}.Release|x64.ActiveCfg = Release|x64 + {D52F964B-C636-4DCC-AA7E-EE83A7AC41AD}.Release|x64.Build.0 = Release|x64 + {D52F964B-C636-4DCC-AA7E-EE83A7AC41AD}.Release|x86.ActiveCfg = Release|Win32 + {D52F964B-C636-4DCC-AA7E-EE83A7AC41AD}.Release|x86.Build.0 = Release|Win32 + {471E64C7-2C65-4E16-A82D-4BF22AE690DD}.Debug|x64.ActiveCfg = Debug|x64 + {471E64C7-2C65-4E16-A82D-4BF22AE690DD}.Debug|x64.Build.0 = Debug|x64 + {471E64C7-2C65-4E16-A82D-4BF22AE690DD}.Debug|x86.ActiveCfg = Debug|Win32 + {471E64C7-2C65-4E16-A82D-4BF22AE690DD}.Debug|x86.Build.0 = Debug|Win32 + {471E64C7-2C65-4E16-A82D-4BF22AE690DD}.Release|x64.ActiveCfg = Release|x64 + {471E64C7-2C65-4E16-A82D-4BF22AE690DD}.Release|x64.Build.0 = Release|x64 + {471E64C7-2C65-4E16-A82D-4BF22AE690DD}.Release|x86.ActiveCfg = Release|Win32 + {471E64C7-2C65-4E16-A82D-4BF22AE690DD}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FB4A98A9-4B75-40C4-B6F2-145D57FC54C4} + EndGlobalSection +EndGlobal diff --git a/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests/CmdLineParser/CmdLineParser.vcxproj b/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests/CmdLineParser/CmdLineParser.vcxproj new file mode 100644 index 0000000..65f478b --- /dev/null +++ b/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests/CmdLineParser/CmdLineParser.vcxproj @@ -0,0 +1,174 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + 15.0 + {54186266-8BA1-438C-AE76-AD64503CA6E9} + Win32Proj + CmdLineParser + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + + Level3 + Disabled + true + false + + + Windows + true + Common.lib;CmdLineParser.lib;XmlProfileParser.lib;powrprof.lib;%(AdditionalDependencies) + $(OutDir);%(AdditionalLibraryDirectories) + + + + + Level3 + Disabled + true + false + + + Windows + true + Common.lib;CmdLineParser.lib;XmlProfileParser.lib;powrprof.lib;%(AdditionalDependencies) + $(OutDir);%(AdditionalLibraryDirectories) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + Common.lib;CmdLineParser.lib;XmlProfileParser.lib;powrprof.lib;%(AdditionalDependencies) + $(OutDir);%(AdditionalLibraryDirectories) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + Common.lib;CmdLineParser.lib;XmlProfileParser.lib;powrprof.lib;%(AdditionalDependencies) + $(OutDir);%(AdditionalLibraryDirectories) + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests/Common/Common.vcxproj b/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests/Common/Common.vcxproj new file mode 100644 index 0000000..3451a51 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests/Common/Common.vcxproj @@ -0,0 +1,174 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + 15.0 + {BA9F561C-B103-48C9-A7C8-CE2B6BD89511} + Win32Proj + Common + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + + Level3 + Disabled + true + false + + + Windows + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + true + false + + + Windows + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;powrprof.lib;%(AdditionalDependencies) + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests/IORequestGenerator/IORequestGenerator.vcxproj b/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests/IORequestGenerator/IORequestGenerator.vcxproj new file mode 100644 index 0000000..aadbf24 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests/IORequestGenerator/IORequestGenerator.vcxproj @@ -0,0 +1,174 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + 15.0 + {13683A8B-2641-4287-9D66-A87834885057} + Win32Proj + IORequestGenerator + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + + Level3 + Disabled + true + false + + + Windows + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;IORequestGenerator.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + true + false + + + Windows + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;IORequestGenerator.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;IORequestGenerator.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;IORequestGenerator.lib;powrprof.lib;%(AdditionalDependencies) + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests/ResultParser/ResultParser.vcxproj b/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests/ResultParser/ResultParser.vcxproj new file mode 100644 index 0000000..b8d4b9d --- /dev/null +++ b/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests/ResultParser/ResultParser.vcxproj @@ -0,0 +1,174 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + 15.0 + {471E64C7-2C65-4E16-A82D-4BF22AE690DD} + Win32Proj + ResultParser + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + + Level3 + Disabled + true + false + + + Windows + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;ResultParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + true + false + + + Windows + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;ResultParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;ResultParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;ResultParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests/XmlProfileParser/XmlProfileParser.vcxproj b/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests/XmlProfileParser/XmlProfileParser.vcxproj new file mode 100644 index 0000000..77e2919 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests/XmlProfileParser/XmlProfileParser.vcxproj @@ -0,0 +1,174 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + 15.0 + {B20AA8CF-ADFB-487C-B8F9-DBD3037F53AD} + Win32Proj + XmlProfileParser + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + + Level3 + Disabled + true + false + + + Windows + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;XmlProfileParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + true + false + + + Windows + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;XmlProfileParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;XmlProfileParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;XmlProfileParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests/XmlResultParser/XmlResultParser.vcxproj b/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests/XmlResultParser/XmlResultParser.vcxproj new file mode 100644 index 0000000..9ff39df --- /dev/null +++ b/CristalDiskMark/source/diskspd22/diskspd_vs/UnitTests/XmlResultParser/XmlResultParser.vcxproj @@ -0,0 +1,174 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + 15.0 + {D52F964B-C636-4DCC-AA7E-EE83A7AC41AD} + Win32Proj + XmlResultParser + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + + Level3 + Disabled + true + false + + + Windows + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;XmlResultParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + true + false + + + Windows + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;XmlResultParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;XmlResultParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;XmlResultParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/diskspd_vs/XmlProfileParser/XmlProfileParser.vcxproj b/CristalDiskMark/source/diskspd22/diskspd_vs/XmlProfileParser/XmlProfileParser.vcxproj new file mode 100644 index 0000000..f2e66f9 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/diskspd_vs/XmlProfileParser/XmlProfileParser.vcxproj @@ -0,0 +1,275 @@ + + + + + Debug + ARM + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {EFF06674-B068-45F1-9661-DB9363B025B3} + XmlProfileParser + 10.0 + + + + StaticLibrary + true + Unicode + v143 + + + StaticLibrary + true + Unicode + v143 + + + StaticLibrary + true + Unicode + v143 + + + StaticLibrary + true + Unicode + v143 + + + StaticLibrary + false + true + Unicode + v143 + + + StaticLibrary + false + true + Unicode + v143 + + + StaticLibrary + false + true + Unicode + v143 + + + StaticLibrary + false + true + Unicode + v143 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + true + true + true + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/diskspd_vs/XmlResultParser/XmlResultParser.vcxproj b/CristalDiskMark/source/diskspd22/diskspd_vs/XmlResultParser/XmlResultParser.vcxproj new file mode 100644 index 0000000..dab830f --- /dev/null +++ b/CristalDiskMark/source/diskspd22/diskspd_vs/XmlResultParser/XmlResultParser.vcxproj @@ -0,0 +1,301 @@ + + + + + Debug + ARM + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {60A28E9C-C245-4D99-9C1C-EC911031743F} + Win32Proj + XmlResultParser + 10.0 + + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + false + true + + + Windows + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + false + true + + + Windows + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + false + true + + + Windows + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + false + true + + + Windows + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + Windows + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + Windows + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + Windows + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + Windows + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd22/diskspd_vs/diskspd.sln b/CristalDiskMark/source/diskspd22/diskspd_vs/diskspd.sln new file mode 100644 index 0000000..9d2e9b7 --- /dev/null +++ b/CristalDiskMark/source/diskspd22/diskspd_vs/diskspd.sln @@ -0,0 +1,159 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35913.81 d17.13 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CmdLineParser", "CmdLineParser\CmdLineParser.vcxproj", "{0EF5CE78-8E92-4A1B-A255-0F544AADA291}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CmdRequestCreator", "CmdRequestCreator\CmdRequestCreator.vcxproj", "{D238F8AA-DE12-49E7-B4A7-9B69579A69C0}" + ProjectSection(ProjectDependencies) = postProject + {B253AB42-F482-417A-82CE-EDAFCD26F366} = {B253AB42-F482-417A-82CE-EDAFCD26F366} + {EFF06674-B068-45F1-9661-DB9363B025B3} = {EFF06674-B068-45F1-9661-DB9363B025B3} + {0EF5CE78-8E92-4A1B-A255-0F544AADA291} = {0EF5CE78-8E92-4A1B-A255-0F544AADA291} + {62DB1E99-FBA0-45FD-9355-423059BA03B8} = {62DB1E99-FBA0-45FD-9355-423059BA03B8} + {60A28E9C-C245-4D99-9C1C-EC911031743F} = {60A28E9C-C245-4D99-9C1C-EC911031743F} + {F6C211DC-B076-4716-BCDC-D7DE88973B66} = {F6C211DC-B076-4716-BCDC-D7DE88973B66} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IORequestGenerator", "IORequestGenerator\IORequestGenerator.vcxproj", "{62DB1E99-FBA0-45FD-9355-423059BA03B8}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ResultParser", "ResultParser\ResultParser.vcxproj", "{F6C211DC-B076-4716-BCDC-D7DE88973B66}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "XmlProfileParser", "XmlProfileParser\XmlProfileParser.vcxproj", "{EFF06674-B068-45F1-9661-DB9363B025B3}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "Common\Common.vcxproj", "{B253AB42-F482-417A-82CE-EDAFCD26F366}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "XmlResultParser", "XmlResultParser\XmlResultParser.vcxproj", "{60A28E9C-C245-4D99-9C1C-EC911031743F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Debug|ARM.ActiveCfg = Debug|ARM + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Debug|ARM.Build.0 = Debug|ARM + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Debug|ARM64.Build.0 = Debug|ARM64 + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Debug|Win32.ActiveCfg = Debug|Win32 + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Debug|Win32.Build.0 = Debug|Win32 + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Debug|x64.ActiveCfg = Debug|x64 + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Debug|x64.Build.0 = Debug|x64 + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Release|ARM.ActiveCfg = Release|ARM + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Release|ARM.Build.0 = Release|ARM + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Release|ARM64.ActiveCfg = Release|ARM64 + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Release|ARM64.Build.0 = Release|ARM64 + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Release|Win32.ActiveCfg = Release|Win32 + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Release|Win32.Build.0 = Release|Win32 + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Release|x64.ActiveCfg = Release|x64 + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Release|x64.Build.0 = Release|x64 + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Debug|ARM.ActiveCfg = Debug|ARM + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Debug|ARM.Build.0 = Debug|ARM + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Debug|ARM64.Build.0 = Debug|ARM64 + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Debug|Win32.ActiveCfg = Debug|Win32 + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Debug|Win32.Build.0 = Debug|Win32 + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Debug|x64.ActiveCfg = Debug|x64 + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Debug|x64.Build.0 = Debug|x64 + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Release|ARM.ActiveCfg = Release|ARM + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Release|ARM.Build.0 = Release|ARM + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Release|ARM64.ActiveCfg = Release|ARM64 + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Release|ARM64.Build.0 = Release|ARM64 + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Release|Win32.ActiveCfg = Release|Win32 + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Release|Win32.Build.0 = Release|Win32 + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Release|x64.ActiveCfg = Release|x64 + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Release|x64.Build.0 = Release|x64 + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Debug|ARM.ActiveCfg = Debug|ARM + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Debug|ARM.Build.0 = Debug|ARM + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Debug|ARM64.Build.0 = Debug|ARM64 + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Debug|Win32.ActiveCfg = Debug|Win32 + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Debug|Win32.Build.0 = Debug|Win32 + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Debug|x64.ActiveCfg = Debug|x64 + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Debug|x64.Build.0 = Debug|x64 + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Release|ARM.ActiveCfg = Release|ARM + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Release|ARM.Build.0 = Release|ARM + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Release|ARM64.ActiveCfg = Release|ARM64 + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Release|ARM64.Build.0 = Release|ARM64 + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Release|Win32.ActiveCfg = Release|Win32 + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Release|Win32.Build.0 = Release|Win32 + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Release|x64.ActiveCfg = Release|x64 + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Release|x64.Build.0 = Release|x64 + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Debug|ARM.ActiveCfg = Debug|ARM + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Debug|ARM.Build.0 = Debug|ARM + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Debug|ARM64.Build.0 = Debug|ARM64 + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Debug|Win32.ActiveCfg = Debug|Win32 + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Debug|Win32.Build.0 = Debug|Win32 + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Debug|x64.ActiveCfg = Debug|x64 + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Debug|x64.Build.0 = Debug|x64 + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Release|ARM.ActiveCfg = Release|ARM + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Release|ARM.Build.0 = Release|ARM + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Release|ARM64.ActiveCfg = Release|ARM64 + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Release|ARM64.Build.0 = Release|ARM64 + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Release|Win32.ActiveCfg = Release|Win32 + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Release|Win32.Build.0 = Release|Win32 + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Release|x64.ActiveCfg = Release|x64 + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Release|x64.Build.0 = Release|x64 + {EFF06674-B068-45F1-9661-DB9363B025B3}.Debug|ARM.ActiveCfg = Debug|ARM + {EFF06674-B068-45F1-9661-DB9363B025B3}.Debug|ARM.Build.0 = Debug|ARM + {EFF06674-B068-45F1-9661-DB9363B025B3}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {EFF06674-B068-45F1-9661-DB9363B025B3}.Debug|ARM64.Build.0 = Debug|ARM64 + {EFF06674-B068-45F1-9661-DB9363B025B3}.Debug|Win32.ActiveCfg = Debug|Win32 + {EFF06674-B068-45F1-9661-DB9363B025B3}.Debug|Win32.Build.0 = Debug|Win32 + {EFF06674-B068-45F1-9661-DB9363B025B3}.Debug|x64.ActiveCfg = Debug|x64 + {EFF06674-B068-45F1-9661-DB9363B025B3}.Debug|x64.Build.0 = Debug|x64 + {EFF06674-B068-45F1-9661-DB9363B025B3}.Release|ARM.ActiveCfg = Release|ARM + {EFF06674-B068-45F1-9661-DB9363B025B3}.Release|ARM.Build.0 = Release|ARM + {EFF06674-B068-45F1-9661-DB9363B025B3}.Release|ARM64.ActiveCfg = Release|ARM64 + {EFF06674-B068-45F1-9661-DB9363B025B3}.Release|ARM64.Build.0 = Release|ARM64 + {EFF06674-B068-45F1-9661-DB9363B025B3}.Release|Win32.ActiveCfg = Release|Win32 + {EFF06674-B068-45F1-9661-DB9363B025B3}.Release|Win32.Build.0 = Release|Win32 + {EFF06674-B068-45F1-9661-DB9363B025B3}.Release|x64.ActiveCfg = Release|x64 + {EFF06674-B068-45F1-9661-DB9363B025B3}.Release|x64.Build.0 = Release|x64 + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Debug|ARM.ActiveCfg = Debug|ARM + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Debug|ARM.Build.0 = Debug|ARM + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Debug|ARM64.Build.0 = Debug|ARM64 + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Debug|Win32.ActiveCfg = Debug|Win32 + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Debug|Win32.Build.0 = Debug|Win32 + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Debug|x64.ActiveCfg = Debug|x64 + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Debug|x64.Build.0 = Debug|x64 + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Release|ARM.ActiveCfg = Release|ARM + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Release|ARM.Build.0 = Release|ARM + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Release|ARM64.ActiveCfg = Release|ARM64 + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Release|ARM64.Build.0 = Release|ARM64 + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Release|Win32.ActiveCfg = Release|Win32 + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Release|Win32.Build.0 = Release|Win32 + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Release|x64.ActiveCfg = Release|x64 + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Release|x64.Build.0 = Release|x64 + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Debug|ARM.ActiveCfg = Debug|ARM + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Debug|ARM.Build.0 = Debug|ARM + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Debug|ARM64.Build.0 = Debug|ARM64 + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Debug|Win32.ActiveCfg = Debug|Win32 + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Debug|Win32.Build.0 = Debug|Win32 + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Debug|x64.ActiveCfg = Debug|x64 + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Debug|x64.Build.0 = Debug|x64 + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Release|ARM.ActiveCfg = Release|ARM + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Release|ARM.Build.0 = Release|ARM + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Release|ARM64.ActiveCfg = Release|ARM64 + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Release|ARM64.Build.0 = Release|ARM64 + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Release|Win32.ActiveCfg = Release|Win32 + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Release|Win32.Build.0 = Release|Win32 + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Release|x64.ActiveCfg = Release|x64 + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D8A6AD59-8AE6-4175-B290-FC85D63D3503} + EndGlobalSection +EndGlobal diff --git a/CristalDiskMark/source/diskspd2_0_15a/CmdLineParser/CmdLineParser.cpp b/CristalDiskMark/source/diskspd2_0_15a/CmdLineParser/CmdLineParser.cpp new file mode 100644 index 0000000..a1b88ae --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/CmdLineParser/CmdLineParser.cpp @@ -0,0 +1,1120 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "CmdLineParser.h" +#include "Common.h" +#include "..\XmlProfileParser\XmlProfileParser.h" +#include +#include +#include + +CmdLineParser::CmdLineParser() : + _dwBlockSize(64 * 1024), + _ulWriteRatio(0), + _hEventStarted(nullptr), + _hEventFinished(nullptr) +{ +} + + +CmdLineParser::~CmdLineParser() +{ +} + +// Get size in bytes from a string (it can end with K, M, G for KB, MB, GB and b for block) +bool CmdLineParser::_GetSizeInBytes(const char *pszSize, UINT64& ullSize) const +{ + bool fOk = true; + UINT64 ullResult = 0; + bool fLastCharacterFound = false; + for (char ch = *pszSize; fOk && (ch != '\0'); ch = *(++pszSize)) + { + if (fLastCharacterFound) + { + fOk = false; + } + else if ((ch >= '0') && (ch <= '9')) + { + if (ullResult <= (MAXUINT64 - (ch - '0')) / 10) + { + ullResult = ((ullResult * 10) + (ch - '0')); + } + else + { + fOk = false; + } + + } + else + { + ch = static_cast(toupper(ch)); + if ((ch == 'B') || (ch == 'K') || (ch == 'M') || (ch == 'G')) + { + UINT64 ullMultiplier = 0; + if (ch == 'B') { ullMultiplier = _dwBlockSize; } + else if (ch == 'K') { ullMultiplier = 1024; } + else if (ch == 'M') { ullMultiplier = 1024 * 1024; } + else if (ch == 'G') { ullMultiplier = 1024 * 1024 * 1024; } + + if (ullResult <= MAXUINT64 / ullMultiplier) + { + ullResult = ullResult * ullMultiplier; + fLastCharacterFound = true; + } + else + { + // overflow + fOk = false; + } + } + else + { + fOk = false; + fprintf(stderr, "Invalid size specifier '%c'. Valid ones are: K - KB, M - MB, G - GB, B - block\n", ch); + } + } + } + + if (fOk) + { + ullSize = ullResult; + } + return fOk; +} + +bool CmdLineParser::_GetRandomDataWriteBufferData(const string& sArg, UINT64& cb, string& sPath) +{ + bool fOk = true; + size_t iComma = sArg.find(','); + if (iComma == sArg.npos) + { + fOk = _GetSizeInBytes(sArg.c_str(), cb); + sPath = ""; + } + else + { + fOk = _GetSizeInBytes(sArg.substr(0, iComma).c_str(), cb); + sPath = sArg.substr(iComma + 1); + } + return fOk; +} + +void CmdLineParser::_DisplayUsageInfo(const char *pszFilename) const +{ + // ISSUE-REVIEW: this formats badly in the default 80 column command prompt + printf("\n"); + printf("Usage: %s [options] target1 [ target2 [ target3 ...] ]\n", pszFilename); + printf("version %s (%s)\n", DISKSPD_NUMERIC_VERSION_STRING, DISKSPD_DATE_VERSION_STRING); + printf("\n"); + printf("Available targets:\n"); + printf(" file_path\n"); + printf(" #\n"); + printf(" :\n"); + printf("\n"); + printf("Available options:\n"); + printf(" -? display usage information\n"); + printf(" -a#[,#[...]] advanced CPU affinity - affinitize threads to CPUs provided after -a in a round-robin\n"); + printf(" manner within current Processor Group (CPU count starts with 0); the same CPU can\n"); + printf(" be listed more than once and the number of CPUs can be different than the number\n"); + printf(" of files or threads\n"); + printf(" [default: round-robin within the current Processor Group starting at CPU 0,\n"); + printf(" use -n to disable default affinity]\n"); + printf(" -ag group affinity - affinitize threads in a round-robin manner across Processor\n"); + printf(" Groups, starting at group 0\n"); + printf(" -b[K|M|G] block size in bytes or KiB/MiB/GiB [default=64K]\n"); + printf(" -B[K|M|G|b] base target offset in bytes or KiB/MiB/GiB/blocks [default=0]\n"); + printf(" (offset from the beginning of the file)\n"); + printf(" -c[K|M|G|b] create files of the given size.\n"); + printf(" Size can be stated in bytes or KiB/MiB/GiB/blocks\n"); + printf(" -C cool down time - duration of the test after measurements finished [default=0s].\n"); + printf(" -D Capture IOPs statistics in intervals of ; these are per-thread\n"); + printf(" per-target: text output provides IOPs standard deviation, XML provides the full\n"); + printf(" IOPs time series in addition. [default=1000, 1 second].\n"); + printf(" -d duration (in seconds) to run test [default=10s]\n"); + printf(" -f[K|M|G|b] target size - use only the first bytes or KiB/MiB/GiB/blocks of the file/disk/partition,\n"); + printf(" for example to test only the first sectors of a disk\n"); + printf(" -fr open file with the FILE_FLAG_RANDOM_ACCESS hint\n"); + printf(" -fs open file with the FILE_FLAG_SEQUENTIAL_SCAN hint\n"); + printf(" -F total number of threads (conflicts with -t)\n"); + printf(" -g throughput per-thread per-target throttled to given bytes per millisecond\n"); + printf(" note that this can not be specified when using completion routines\n"); + printf(" [default inactive]\n"); + printf(" -h disable both software caching and hardware write caching. Equivalent to\n"); + printf(" FILE_FLAG_NO_BUFFERING and FILE_FLAG_WRITE_THROUGH\n"); + printf(" [default: caching is enabled, also see -S]\n"); + printf(" -i number of IOs per burst; see -j [default: inactive]\n"); + printf(" -j interval in between issuing IO bursts; see -i [default: inactive]\n"); + printf(" -I Set IO priority to . Available values are: 1-very low, 2-low, 3-normal (default)\n"); + printf(" -l Use large pages for IO buffers\n"); + printf(" -L measure latency statistics\n"); + printf(" -n disable default affinity (-a)\n"); + printf(" -o number of outstanding I/O requests per target per thread\n"); + printf(" (1=synchronous I/O, unless more than 1 thread is specified with -F)\n"); + printf(" [default=2]\n"); + printf(" -p start parallel sequential I/O operations with the same offset\n"); + printf(" (ignored if -r is specified, makes sense only with -o2 or greater)\n"); + printf(" -P enable printing a progress dot after each [default=65536]\n"); + printf(" completed I/O operations, counted separately by each thread \n"); + printf(" -r[K|M|G|b] random I/O aligned to in bytes/KiB/MiB/GiB/blocks (overrides -s)\n"); + printf(" -R output format. Default is text.\n"); + printf(" -s[i][K|M|G|b] sequential stride size, offset between subsequent I/O operations\n"); + printf(" [default access=non-interlocked sequential, default stride=block size]\n"); + printf(" In non-interlocked mode, threads do not coordinate, so the pattern of offsets\n"); + printf(" as seen by the target will not be truly sequential. Under -si the threads\n"); + printf(" manipulate a shared offset with InterlockedIncrement, which may reduce throughput,\n"); + printf(" but promotes a more sequential pattern.\n"); + printf(" (ignored if -r specified, -si conflicts with -T and -p)\n"); + printf(" -S disable software caching, equivalent to FILE_FLAG_NO_BUFFERING\n"); + printf(" [default: caching is enabled, also see -h]\n"); + printf(" -t number of threads per target (conflicts with -F)\n"); + printf(" -T[K|M|G|b] starting stride between I/O operations performed on the same target by different threads\n"); + printf(" [default=0] (starting offset = base file offset + (thread number * )\n"); + printf(" makes sense only with #threads > 1\n"); + printf(" -v verbose mode\n"); + printf(" -w percentage of write requests (-w and -w0 are equivalent and result in a read-only workload).\n"); + printf(" absence of this switch indicates 100%% reads\n"); + printf(" IMPORTANT: a write test will destroy existing data without a warning\n"); + printf(" -W warm up time - duration of the test before measurements start [default=5s]\n"); + printf(" -x use completion routines instead of I/O Completion Ports\n"); + printf(" -X use an XML file for configuring the workload. Cannot be used with other parameters.\n"); + printf(" -z[seed] set random seed [with no -z, seed=0; with plain -z, seed is based on system run time]\n"); + printf("\n"); + printf("Write buffers:\n"); + printf(" -Z zero buffers used for write tests\n"); + printf(" -Z[K|M|G|b] use a buffer filled with random data as a source for write operations.\n"); + printf(" -Z[K|M|G|b], use a buffer filled with data from as a source for write operations.\n"); + printf("\n"); + printf(" By default, the write buffers are filled with a repeating pattern (0, 1, 2, ..., 255, 0, 1, ...)\n"); + printf("\n"); + printf("Synchronization:\n"); + printf(" -ys signals event before starting the actual run (no warmup)\n"); + printf(" (creates a notification event if does not exist)\n"); + printf(" -yf signals event after the actual run finishes (no cooldown)\n"); + printf(" (creates a notification event if does not exist)\n"); + printf(" -yr waits on event before starting the run (including warmup)\n"); + printf(" (creates a notification event if does not exist)\n"); + printf(" -yp stops the run when event is set; CTRL+C is bound to this event\n"); + printf(" (creates a notification event if does not exist)\n"); + printf(" -ye sets event and quits\n"); + printf("\n"); + printf("Event Tracing:\n"); + printf(" -e Use query perf timer (qpc), cycle count, or system timer respectively.\n"); + printf(" [default = q, query perf timer (qpc)]\n"); + printf(" -ep use paged memory for the NT Kernel Logger [default=non-paged memory]\n"); + printf(" -ePROCESS process start & end\n"); + printf(" -eTHREAD thread start & end\n"); + printf(" -eIMAGE_LOAD image load\n"); + printf(" -eDISK_IO physical disk IO\n"); + printf(" -eMEMORY_PAGE_FAULTS all page faults\n"); + printf(" -eMEMORY_HARD_FAULTS hard faults only\n"); + printf(" -eNETWORK TCP/IP, UDP/IP send & receive\n"); + printf(" -eREGISTRY registry calls\n"); + printf("\n\n"); + printf("Examples:\n\n"); + printf("Create 8192KB file and run read test on it for 1 second:\n\n"); + printf(" %s -c8192K -d1 testfile.dat\n", pszFilename); + printf("\n"); + printf("Set block size to 4KB, create 2 threads per file, 32 overlapped (outstanding)\n"); + printf("I/O operations per thread, disable all caching mechanisms and run block-aligned random\n"); + printf("access read test lasting 10 seconds:\n\n"); + printf(" %s -b4K -t2 -r -o32 -d10 -h testfile.dat\n\n", pszFilename); + printf("Create two 1GB files, set block size to 4KB, create 2 threads per file, affinitize threads\n"); + printf("to CPUs 0 and 1 (each file will have threads affinitized to both CPUs) and run read test\n"); + printf("lasting 10 seconds:\n\n"); + printf(" %s -c1G -b4K -t2 -d10 -a0,1 testfile1.dat testfile2.dat\n", pszFilename); + + printf("\n"); +} + +bool CmdLineParser::_ParseETWParameter(const char *arg, Profile *pProfile) +{ + assert(nullptr != arg); + assert(0 != *arg); + + bool fOk = true; + pProfile->SetEtwEnabled(true); + if (*(arg + 1) != '\0') + { + const char *c = arg + 1; + if (*c == 'p') + { + pProfile->SetEtwUsePagedMemory(true); + } + else if (*c == 'q') + { + pProfile->SetEtwUsePerfTimer(true); + } + else if (*c == 's') + { + pProfile->SetEtwUseSystemTimer(true); //default + } + else if (*c == 'c') + { + pProfile->SetEtwUseCyclesCounter(true); + } + else if (strcmp(c, "PROCESS") == 0) //process start & end + { + pProfile->SetEtwProcess(true); + } + else if (strcmp(c, "THREAD") == 0) //thread start & end + { + pProfile->SetEtwThread(true); + } + else if (strcmp(c, "IMAGE_LOAD") == 0) //image load + { + pProfile->SetEtwImageLoad(true); + } + else if (strcmp(c, "DISK_IO") == 0) //physical disk IO + { + pProfile->SetEtwDiskIO(true); + } + else if (strcmp(c, "MEMORY_PAGE_FAULTS") == 0) //all page faults + { + pProfile->SetEtwMemoryPageFaults(true); + } + else if (strcmp(c, "MEMORY_HARD_FAULTS") == 0) //hard faults only + { + pProfile->SetEtwMemoryHardFaults(true); + } + else if (strcmp(c, "NETWORK") == 0) //tcpip send & receive + { + pProfile->SetEtwNetwork(true); + } + else if (strcmp(c, "REGISTRY") == 0) //registry calls + { + pProfile->SetEtwRegistry(true); + } + else + { + fOk = false; + } + } + else + { + fOk = false; + } + + return fOk; +} + +bool CmdLineParser::_ParseAffinity(const char *arg, TimeSpan *pTimeSpan) +{ + bool fOk = true; + + assert(nullptr != arg); + assert('\0' != *arg); + + const char *c = arg + 1; + if (*c == '\0') + { + // simple affinity (-a) is turned on by default, do nothing + return false; + } + + if (*c == 'g') + { + pTimeSpan->SetGroupAffinity(true); + return true; + } + + // TODO: will treat ,, as ,0, + // more complex affinity (-a#[,#[,#...]]) + int nCpu = 0; + while (fOk && (*c != '\0')) + { + if ((*c >= '0') && (*c <= '9')) + { + nCpu = 10 * nCpu + (*c - '0'); + } + else if (*c == ',') + { + pTimeSpan->AddAffinityAssignment(nCpu); + nCpu = 0; + } + else + { + fOk = false; + fprintf(stderr, "error parsing affinity (invalid character: %c)\n", *c); + } + c++; + } + if (fOk) + { + pTimeSpan->AddAffinityAssignment(nCpu); + } + + return fOk; +} + +bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[], Profile *pProfile, struct Synchronization *synch) +{ + /* Process any command-line options */ + int nParamCnt = argc - 1; + const char** args = argv + 1; + + // create targets + vector vTargets; + int iFirstFile = -1; + for (int i = 1; i < argc; i++) + { + if (argv[i][0] != '-' && argv[i][0] != '/') + { + iFirstFile = i; + Target target; + target.SetPath(argv[i]); + vTargets.push_back(target); + } + } + + // find block size (other parameters may be stated in terms of blocks) + for (int x = 1; x < argc; ++x) + { + if ((nullptr != argv[x]) && (('-' == argv[x][0]) || ('/' == argv[x][0])) && ('b' == argv[x][1]) && ('\0' != argv[x][2])) + { + _dwBlockSize = 0; + UINT64 ullBlockSize; + if (_GetSizeInBytes(&argv[x][2], ullBlockSize)) + { + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + // TODO: UINT64->DWORD + i->SetBlockSizeInBytes((DWORD)ullBlockSize); + } + } + else + { + fprintf(stderr, "Invalid block size passed to -b\n"); + exit(1); + } + _dwBlockSize = (DWORD)ullBlockSize; + break; + } + } + + TimeSpan timeSpan; + bool bExit = false; + while (nParamCnt) + { + const char* arg = *args; + bool fError = false; + + // check if it is a parameter or already path + if ('-' != *arg && '/' != *arg) + { + break; + } + + // skip '-' or '/' + ++arg; + + if ('\0' == *arg) + { + fprintf(stderr, "Invalid option\n"); + exit(1); + } + + switch (*arg) + { + case '?': + _DisplayUsageInfo(argv[0]); + exit(0); + + case 'A': /// CrystalDiskMark Process ID + extern DWORD pid; + pid = (DWORD)strtoul(arg + 1, NULL, 10); + break; + + case 'a': //affinity + //-a1,2,3,4 (assign threads to cpus 1,2,3,4 (round robin)) + if (!_ParseAffinity(arg, &timeSpan)) + { + fError = true; + } + break; + + case 'b': //block size + // nop - block size has been taken care of before the loop + break; + + case 'B': //base file offset (offset from the beggining of the file), cannot be used with 'random' + if (*(arg + 1) != '\0') + { + UINT64 cb; + if (_GetSizeInBytes(arg + 1, cb)) + { + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetBaseFileOffsetInBytes(cb); + } + } + else + { + fprintf(stderr, "Invalid base file offset passed to -B\n"); + fError = true; + } + } + else + { + fError = true; + } + break; + + case 'c': //create file of the given size + if (*(arg + 1) != '\0') + { + UINT64 cb; + if (_GetSizeInBytes(arg + 1, cb)) + { + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetFileSize(cb); + i->SetCreateFile(true); + } + } + else + { + fprintf(stderr, "Invalid file size passed to -c\n"); + fError = true; + } + } + else + { + fError = true; + } + break; + + case 'C': //cool down time + { + int c = atoi(arg + 1); + if (c >= 0) + { + timeSpan.SetCooldown(c); + } + else + { + fError = true; + } + } + break; + + case 'd': //duration + { + int x = atoi(arg + 1); + if (x > 0) + { + timeSpan.SetDuration(x); + } + else + { + fError = true; + } + } + break; + + case 'D': //standard deviation + { + timeSpan.SetCalculateIopsStdDev(true); + + int x = atoi(arg + 1); + if (x > 0) + { + timeSpan.SetIoBucketDurationInMilliseconds(x); + } + } + break; + + case 'e': //etw + if (!_ParseETWParameter(arg, pProfile)) + { + fError = true; + } + break; + + case 'f': + if ('r' == *(arg + 1)) + { + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetRandomAccessHint(true); + } + } + else if ('s' == *(arg + 1)) + { + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetSequentialScanHint(true); + } + } + else + { + if (*(arg + 1) != '\0') + { + UINT64 cb; + if (_GetSizeInBytes(arg + 1, cb)) + { + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetMaxFileSize(cb); + } + } + else + { + fprintf(stderr, "Invalid max file size passed to -f\n"); + fError = true; + } + } + else + { + fError = true; + } + } + break; + + case 'F': //total number of threads + { + int c = atoi(arg + 1); + if (c > 0) + { + timeSpan.SetThreadCount(c); + } + else + { + fError = true; + } + } + break; + + case 'g': //throughput in bytes per millisecond + { + int c = atoi(arg + 1); + if (c > 0) + { + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetThroughput(c); + } + } + else + { + fError = true; + } + } + break; + + case 'h': //disable both software and hardware caching + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetDisableAllCache(true); + } + break; + + case 'i': //number of IOs to issue before think time + { + int c = atoi(arg + 1); + if (c > 0) + { + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetBurstSize(c); + i->SetUseBurstSize(true); + } + } + else + { + fError = true; + } + } + break; + + case 'j': //time to wait between bursts of IOs + { + int c = atoi(arg + 1); + if (c > 0) + { + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetThinkTime(c); + i->SetEnableThinkTime(true); + } + } + else + { + fError = true; + } + } + break; + + case 'I': //io priority + { + int x = atoi(arg + 1); + if (x > 0 && x < 4) + { + PRIORITY_HINT hint[] = { IoPriorityHintVeryLow, IoPriorityHintLow, IoPriorityHintNormal }; + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetIOPriorityHint(hint[x - 1]); + } + } + else + { + fError = true; + } + } + break; + + case 'l': //large pages + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetUseLargePages(true); + } + break; + + case 'L': //measure latency + timeSpan.SetMeasureLatency(true); + break; + + case 'n': //disable affinity (by default simple affinity is turned on) + timeSpan.SetDisableAffinity(true); + break; + + case 'o': //request count (1==synchronous) + { + int c = atoi(arg + 1); + if (c > 0) + { + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetRequestCount(c); + } + } + else + { + fError = true; + } + } + break; + + case 'p': //start async IO operations with the same offset + //makes sense only for -o2 and greater + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetUseParallelAsyncIO(true); + } + break; + + case 'P': //show progress every x IO operations + { + int c = atoi(arg + 1); + if (c < 1) + { + c = 65536; + } + pProfile->SetProgress(c); + } + break; + + case 'r': //random access + { + UINT64 cb = _dwBlockSize; + if (*(arg + 1) != '\0') + { + if (!_GetSizeInBytes(arg + 1, cb) || (cb == 0)) + { + fprintf(stderr, "Invalid alignment passed to -r\n"); + fError = true; + } + } + if (!fError) + { + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetUseRandomAccessPattern(true); + i->SetBlockAlignmentInBytes(cb); + } + } + } + break; + + case 'R': //custom result parser + if (0 != *(arg + 1)) + { + const char* pszArg = arg + 1; + if (strcmp(pszArg, "xml") == 0) + { + pProfile->SetResultsFormat(ResultsFormat::Xml); + } + else if (strcmp(pszArg, "text") != 0) + { + fError = true; + fprintf(stderr, "Invalid results format: '%s'.\n", pszArg); + } + } + else + { + fError = true; + } + break; + + case 's': //stride size + { + int idx = 1; + + if ('i' == *(arg + idx)) + { + // do interlocked sequential mode + // ISSUE-REVIEW: this does nothing if -r is specified + // ISSUE-REVIEW: this does nothing if -p is specified + // ISSUE-REVIEW: this does nothing if we are single-threaded + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetUseInterlockedSequential(true); + } + + idx++; + } + + if (*(arg + idx) != '\0') + { + UINT64 cb; + if (_GetSizeInBytes(arg + idx, cb)) + { + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetBlockAlignmentInBytes(cb); + } + } + else + { + fprintf(stderr, "Invalid stride size passed to -s\n"); + fError = true; + } + } + } + break; + + case 'S': //disable OS caching (software buffering) + //IMPORTANT: File access must begin at byte offsets within the file that are integer multiples of the volume's sector size. + // File access must be for numbers of bytes that are integer multiples of the volume's sector size. For example, if the sector + // size is 512 bytes, an application can request reads and writes of 512, 1024, or 2048 bytes, but not of 335, 981, or 7171 bytes. + // Buffer addresses for read and write operations should be sector aligned (aligned on addresses in memory that are integer + // multiples of the volume's sector size). Depending on the disk, this requirement may not be enforced. + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetDisableOSCache(true); + } + break; + + case 't': //number of threads per file + { + int c = atoi(arg + 1); + if (c > 0) + { + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetThreadsPerFile(c); + } + } + else + { + fError = true; + } + } + break; + + case 'T': //offsets between threads reading the same file + { + UINT64 cb; + if (_GetSizeInBytes(arg + 1, cb) && (cb > 0)) + { + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetThreadStrideInBytes(cb); + } + } + else + { + fprintf(stderr, "Invalid offset passed to -T\n"); + fError = true; + } + } + break; + + case 'v': //verbose mode + pProfile->SetVerbose(true); + break; + + case 'w': //write test [default=read] + { + int c = -1; + if (*(arg + 1) == '\0') + { + c = _ulWriteRatio; + } + else + { + c = atoi(arg + 1); + if (c < 0 || c > 100) + { + c = -1; + fError = true; + } + } + if (c != -1) + { + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetWriteRatio(c); + } + } + } + break; + + case 'W': //warm up time + { + int c = atoi(arg + 1); + if (c >= 0) + { + timeSpan.SetWarmup(c); + } + else + { + fError = true; + } + } + break; + + case 'x': //completion routines + timeSpan.SetCompletionRoutines(true); + break; + + case 'y': //external synchronization + switch (*(arg + 1)) + { + + case 's': + _hEventStarted = CreateEvent(NULL, TRUE, FALSE, arg + 2); + if (NULL == _hEventStarted) + { + fprintf(stderr, "Error creating/opening start notification event: '%s'\n", arg + 2); + exit(1); // TODO: this class shouldn't terminate the process + } + break; + + case 'f': + _hEventFinished = CreateEvent(NULL, TRUE, FALSE, arg + 2); + if (NULL == _hEventFinished) + { + fprintf(stderr, "Error creating/opening finish notification event: '%s'\n", arg + 2); + exit(1); // TODO: this class shouldn't terminate the process + } + break; + + case 'r': + synch->hStartEvent = CreateEvent(NULL, TRUE, FALSE, arg + 2); + if (NULL == synch->hStartEvent) + { + fprintf(stderr, "Error creating/opening wait-for-start event: '%s'\n", arg + 2); + exit(1); // TODO: this class shouldn't terminate the process + } + break; + + case 'p': + synch->hStopEvent = CreateEvent(NULL, TRUE, FALSE, arg + 2); + if (NULL == synch->hStopEvent) + { + fprintf(stderr, "Error creating/opening force-stop event: '%s'\n", arg + 2); + exit(1); // TODO: this class shouldn't terminate the process + } + break; + + case 'e': + { + HANDLE hEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, arg + 2); + if (NULL == hEvent) + { + fprintf(stderr, "Error opening event '%s'\n", arg + 2); + exit(1); // TODO: this class shouldn't terminate the process + } + if (!SetEvent(hEvent)) + { + fprintf(stderr, "Error setting event '%s'\n", arg + 2); + exit(1); // TODO: this class shouldn't terminate the process + } + CloseHandle(hEvent); + printf("Succesfully set event: '%s'\n", arg + 2); + bExit = true; + break; + } + + default: + fError = true; + } + + case 'z': //random seed + if (*(arg + 1) == '\0') + { + /// GetTickCount64 -> GetTickCount + timeSpan.SetRandSeed(GetTickCount()); + } + else + { + int c = atoi(arg + 1); + if (c >= 0) + { + timeSpan.SetRandSeed(c); + } + else + { + fError = true; + } + } + break; + + case 'Z': //zero write buffers + if (*(arg + 1) == '\0') + { + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetZeroWriteBuffers(true); + } + } + else + { + UINT64 cb = 0; + string sPath; + if (_GetRandomDataWriteBufferData(string(arg + 1), cb, sPath) && (cb > 0)) + { + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->SetRandomDataWriteBufferSize(cb); + i->SetRandomDataWriteBufferSourcePath(sPath); + } + } + else + { + fprintf(stderr, "Invalid size passed to -Z\n"); + fError = true; + } + } + break; + + default: + fprintf(stderr, "Invalid option: '%s'\n", arg); + exit(1); // TODO: this class shouldn't terminate the process + } + + if (fError) + { + fprintf(stderr, "Incorrectly provided option: '%s'\n", arg); + exit(1); // TODO: this class shouldn't terminate the process + } + + --nParamCnt; + ++args; + } + + // + // exit if a user specified an action which was already satisfied and doesn't require running test + // + if (bExit) + { + printf("Now exiting...\n"); + exit(1); // TODO: this class shouldn't terminate the process + } + + if (vTargets.size() < 1) + { + fprintf(stderr, "You need to provide at least one filename\n"); + return false; + } + + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + timeSpan.AddTarget(*i); + } + pProfile->AddTimeSpan(timeSpan); + + return true; +} + +bool CmdLineParser::_ReadParametersFromXmlFile(const char *pszPath, Profile *pProfile) +{ + XmlProfileParser parser; + return parser.ParseFile(pszPath, pProfile); +} + +bool CmdLineParser::ParseCmdLine(const int argc, const char *argv[], Profile *pProfile, struct Synchronization *synch) +{ + assert(nullptr != argv); + assert(nullptr != pProfile); + assert(NULL != synch); + + if (argc < 2) + { + _DisplayUsageInfo(argv[0]); + return false; + } + + string sCmdLine; + for (int i = 0; i < argc - 1; i++) + { + sCmdLine += argv[i]; + sCmdLine += ' '; + } + if (argc > 0) + { + sCmdLine += argv[argc - 1]; + } + pProfile->SetCmdLine(sCmdLine); + + //check if parameters should be read from an xml file + bool fOk = true; + bool fCmdLine; + + if (argc == 2 && (argv[1][0] == '-' || argv[1][0] == '/') && argv[1][1] == 'X' && argv[1][2] != '\0') + { + fOk = _ReadParametersFromXmlFile(argv[1] + 2, pProfile); + fCmdLine = false; + } + else + { + fOk = _ReadParametersFromCmdLine(argc, argv, pProfile, synch); + fCmdLine = true; + } + + // check additional restrictions and conditions on the passed parameters. + // note that on the cmdline, all targets receive the same parameters so + // that their mutual consistency only needs to be checked once. + if (fOk) + { + fOk = pProfile->Validate(fCmdLine); + } + + return fOk; +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/CmdLineParser/CmdLineParser.h b/CristalDiskMark/source/diskspd2_0_15a/CmdLineParser/CmdLineParser.h new file mode 100644 index 0000000..1c7f593 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/CmdLineParser/CmdLineParser.h @@ -0,0 +1,64 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#include "Common.h" + +namespace UnitTests { class CmdLineParserUnitTests; } + +class CmdLineParser +{ +public: + CmdLineParser(); + ~CmdLineParser(); + + bool ParseCmdLine(const int argc, const char *argv[], Profile *pProfile, struct Synchronization *synch); + +private: + bool _ReadParametersFromCmdLine(const int argc, const char *argv[], Profile *pProfile, struct Synchronization *synch); + bool _ReadParametersFromXmlFile(const char *pszPath, Profile *pProfile); + + bool _ParseETWParameter(const char *arg, Profile *pProfile); + bool _ParseAffinity(const char *arg, TimeSpan *pTimeSpan); + + void _DisplayUsageInfo(const char *pszFilename) const; + bool _GetSizeInBytes(const char *pszSize, UINT64& ullSize) const; + bool _GetRandomDataWriteBufferData(const string& sArg, UINT64& cb, string& sPath); + + // variables that used to be global + DWORD _dwBlockSize; // block size; other parameters may be stated in blocks + // so the block size is needed to process them + + UINT32 _ulWriteRatio; // default percentage of write requests + + HANDLE _hEventStarted; // event signalled to notify that the actual (measured) test is to be started + HANDLE _hEventFinished; // event signalled to notify that the actual test has finished + + friend class UnitTests::CmdLineParserUnitTests; +}; diff --git a/CristalDiskMark/source/diskspd2_0_15a/CmdRequestCreator/CmdRequestCreator.cpp b/CristalDiskMark/source/diskspd2_0_15a/CmdRequestCreator/CmdRequestCreator.cpp new file mode 100644 index 0000000..65aac96 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/CmdRequestCreator/CmdRequestCreator.cpp @@ -0,0 +1,219 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +// CmdRequestCreator.cpp : Defines the entry point for the console application. +// + +#include "CmdRequestCreator.h" +#include +#include +#include +#include "common.h" +#include "errors.h" +#include "..\CmdLineParser\CmdLineParser.h" +#include "..\XmlProfileParser\XmlProfileParser.h" +#include "..\IORequestGenerator\IORequestGenerator.h" +#include "..\ResultParser\ResultParser.h" +#include "..\XmlResultParser\XmlResultParser.h" + +/*****************************************************************************/ +// global variables +static HANDLE g_hAbortEvent = NULL; // handle to the 'abort' event + // it allows stopping I/O Request Generator in the middle of its work + // the results of its work will be passed to the Results Parser +static HANDLE g_hEventStarted = NULL; // event signalled to notify that the actual (measured) test is to be started +static HANDLE g_hEventFinished = NULL; // event signalled to notify that the actual test has finished + +/*****************************************************************************/ +// wrapper for printf. printf cannot be used directly, because IORequestGenerator.dll +// may be consumed by gui app which doesn't have stdout +void WINAPI PrintOut(const char *format, va_list args) +{ + vprintf(format, args); +} + +/*****************************************************************************/ +// wrapper for fprintf. fprintf cannot be used directly, because IORequestGenerator.dll +// may be consumed by gui app which doesn't have stdout +void WINAPI PrintError(const char *format, va_list args) +{ + vfprintf(stderr, format, args); +} + +/*****************************************************************************/ +BOOL WINAPI ctrlCRoutine(DWORD dwCtrlType) +{ + if( CTRL_C_EVENT == dwCtrlType ) + { + printf("\n*** Interrupted by Ctrl-C. Stopping I/O Request Generator. ***\n"); + if( !SetEvent(g_hAbortEvent) ) + { + fprintf(stderr, "Warning: Setting abort event failed (error code: %u)\n", GetLastError()); + } + SetConsoleCtrlHandler(ctrlCRoutine, FALSE); + + //indicate that the signal has been handled + return TRUE; + } + else + { + return FALSE; + } +} + +/*****************************************************************************/ +void TestStarted() +{ + if( (NULL != g_hEventStarted) && !SetEvent(g_hEventStarted) ) + { + fprintf(stderr, "Warning: Setting test start notification event failed (error code: %u)\n", GetLastError()); + } +} + +/*****************************************************************************/ +void TestFinished() +{ + if( (NULL != g_hEventFinished) && !SetEvent(g_hEventFinished) ) + { + fprintf(stderr, "Warning: Setting test finish notification event failed (error code: %u)\n", GetLastError()); + } +} + +DWORD pid = 0; + +/*****************************************************************************/ +int __cdecl main(int argc, const char* argv[]) +{ + /// for CrystalDiskMark + int totalScore = 0; + double averageLatency = 0.0; + + // + // parse cmd line parameters + // + struct Synchronization synch; //sychronization structure + synch.ulStructSize = sizeof(synch); + synch.hStopEvent = NULL; + synch.hStartEvent = NULL; + + CmdLineParser cmdLineParser; + Profile profile; + if (!cmdLineParser.ParseCmdLine(argc, argv, &profile, &synch)) + { + return ERROR_PARSE_CMD_LINE; + } + + synch.pfnCallbackTestStarted = TestStarted; + synch.pfnCallbackTestFinished = TestFinished; + + // + // create abort event if stop event is not explicitly provided by the user (otherwise use the stop event) + // + + if (NULL == synch.hStopEvent) + { + synch.hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if( NULL == synch.hStopEvent ) + { + fprintf(stderr, "Unable to create an abort event for CTRL+C\n"); + //FUTURE EXTENSION: change error code + return 5; + } + } + g_hAbortEvent = synch.hStopEvent; // set abort event to either stop event provided by user or the just created event + + // + // capture ctrl+c + // + if( !SetConsoleCtrlHandler(ctrlCRoutine, TRUE) ) + { + fprintf(stderr, "Unable to set CTRL+C routine\n"); + //FUTURE EXTENSION: change error code + return 6; + } + + // + // call IO request generator + // + ResultParser resultParser; + XmlResultParser xmlResultParser; + IResultParser *pResultParser = nullptr; + if (profile.GetResultsFormat() == ResultsFormat::Xml) + { + pResultParser = &xmlResultParser; + } + else + { + pResultParser = &resultParser; + } + + IORequestGenerator ioGenerator; + if (!ioGenerator.GenerateRequests(profile, *pResultParser, (PRINTF)PrintOut, (PRINTF)PrintError, (PRINTF)PrintOut, &synch, &totalScore, &averageLatency)) + { + fprintf(stderr, "Error generating I/O requests\n"); + return 7; + } + + if( NULL != synch.hStartEvent ) + { + CloseHandle(synch.hStartEvent); + } + if( NULL != synch.hStopEvent ) + { + CloseHandle(synch.hStopEvent); + } + if( g_hEventStarted ) + { + CloseHandle(g_hEventStarted); + } + if( NULL != g_hEventFinished ) + { + CloseHandle(g_hEventFinished); + } + + /// for CrystalDiskMark + CHAR name[32]; + snprintf(name, 32, "CrystalDiskMark%08X", pid); + auto size = 8; + + HANDLE hSharedMemory = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, NULL, size, name); + if (hSharedMemory != NULL) + { + auto pMemory = (double*)MapViewOfFile(hSharedMemory, FILE_MAP_ALL_ACCESS, NULL, NULL, size); + if (pMemory != NULL) + { + *pMemory = averageLatency; + UnmapViewOfFile(pMemory); + CloseHandle(hSharedMemory); + } + } + // fprintf(stderr, "%f", averageLatency); + + return totalScore; +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/CmdRequestCreator/CmdRequestCreator.h b/CristalDiskMark/source/diskspd2_0_15a/CmdRequestCreator/CmdRequestCreator.h new file mode 100644 index 0000000..f8d2937 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/CmdRequestCreator/CmdRequestCreator.h @@ -0,0 +1,38 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include +#include \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/CmdRequestCreator/errors.h b/CristalDiskMark/source/diskspd2_0_15a/CmdRequestCreator/errors.h new file mode 100644 index 0000000..f11b5ca --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/CmdRequestCreator/errors.h @@ -0,0 +1,38 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#ifndef __DISKSPD_ERRORS_H +#define __DISKSPD_ERRORS_H + +#define ERROR_LOAD_LIBRARY 1 //error during LoadLibrary call +#define ERROR_GET_PROC_ADDRESS 2 //error during GetProcAddress +#define ERROR_PARSE_CMD_LINE 3 //error parsing cmd line parameters +#define ERROR_WAIT_FOR_START_SIGNAL 4 //error waiting for a signal to start work + +#endif \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/Common/Common.cpp b/CristalDiskMark/source/diskspd2_0_15a/Common/Common.cpp new file mode 100644 index 0000000..2d0c2fc --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/Common/Common.cpp @@ -0,0 +1,685 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "Common.h" + +UINT64 PerfTimer::GetTime() +{ + LARGE_INTEGER li; + QueryPerformanceCounter(&li); + return li.QuadPart; +} + +UINT64 PerfTimer::_GetPerfTimerFreq() +{ + LARGE_INTEGER li; + QueryPerformanceFrequency(&li); + return li.QuadPart; +} + +const UINT64 PerfTimer::TIMER_FREQ = _GetPerfTimerFreq(); + +double PerfTimer::PerfTimeToMicroseconds(const double perfTime) +{ + return perfTime / (TIMER_FREQ / 1000000.0); +} + +double PerfTimer::PerfTimeToMilliseconds(const double perfTime) +{ + return PerfTimeToMicroseconds(perfTime) / 1000; +} + +double PerfTimer::PerfTimeToSeconds(const double perfTime) +{ + return PerfTimeToMilliseconds(perfTime) / 1000; +} + +double PerfTimer::PerfTimeToMicroseconds(const UINT64 perfTime) +{ + return PerfTimeToMicroseconds(static_cast(perfTime)); +} + +double PerfTimer::PerfTimeToMilliseconds(const UINT64 perfTime) +{ + return PerfTimeToMilliseconds(static_cast(perfTime)); +} + +double PerfTimer::PerfTimeToSeconds(const UINT64 perfTime) +{ + return PerfTimeToSeconds(static_cast(perfTime)); +} + +UINT64 PerfTimer::MicrosecondsToPerfTime(const double microseconds) +{ + return static_cast(TIMER_FREQ * (microseconds / 1000000.0)); +} + +UINT64 PerfTimer::MillisecondsToPerfTime(const double milliseconds) +{ + return static_cast(TIMER_FREQ * (milliseconds / 1000.0)); +} + +UINT64 PerfTimer::SecondsToPerfTime(const double seconds) +{ + return static_cast(TIMER_FREQ * seconds); +} + +string Util::DoubleToStringHelper(const double d) +{ + char szFloatBuffer[100]; + sprintf_s(szFloatBuffer, _countof(szFloatBuffer), "%10.3lf", d); + + return string(szFloatBuffer); +} + +string Target::GetXml() const +{ + char buffer[4096]; + string sXml("\n"); + sXml += "" + _sPath + "\n"; + + sprintf_s(buffer, _countof(buffer), "%u\n", _dwBlockSize); + sXml += buffer; + + sprintf_s(buffer, _countof(buffer), "%I64u\n", _ullBaseFileOffset); + sXml += buffer; + + sXml += _fSequentialScanHint ? "true\n" : "false\n"; + sXml += _fRandomAccessHint ? "true\n" : "false\n"; + sXml += _fUseLargePages ? "true\n" : "false\n"; + sXml += _fDisableAllCache ? "true\n" : "false\n"; + // normalize cache controls - disabling the OS cache is included if all caches are disabled, + // so specifying it twice is not neccesary (this generates a warning on the cmdline) + if (!_fDisableAllCache) + { + sXml += _fDisableOSCache ? "true\n" : "false\n"; + } + + sXml += "\n"; + if (_fZeroWriteBuffers) + { + sXml += "zero\n"; + } + else if (_cbRandomDataWriteBuffer == 0) + { + sXml += "sequential\n"; + } + else + { + sXml += "random\n"; + sXml += "\n"; + sprintf_s(buffer, _countof(buffer), "%I64u\n", _cbRandomDataWriteBuffer); + sXml += buffer; + if (_sRandomDataWriteBufferSourcePath != "") + { + sXml += "" + _sRandomDataWriteBufferSourcePath + "\n"; + } + sXml += "\n"; + } + sXml += "\n"; + + sXml += _fParallelAsyncIO ? "true\n" : "false\n"; + + if (_fUseBurstSize) + { + sprintf_s(buffer, _countof(buffer), "%u\n", _dwBurstSize); + sXml += buffer; + } + + if (_fThinkTime) + { + sprintf_s(buffer, _countof(buffer), "%u\n", _dwThinkTime); + sXml += buffer; + } + + if (_fCreateFile) + { + sprintf_s(buffer, _countof(buffer), "%I64u\n", _ullFileSize); + sXml += buffer; + } + + // If XML contains , is ignored + if (_fUseRandomAccessPattern) + { + sprintf_s(buffer, _countof(buffer), "%I64u\n", GetBlockAlignmentInBytes()); + sXml += buffer; + } + else + { + sprintf_s(buffer, _countof(buffer), "%I64u\n", GetBlockAlignmentInBytes()); + sXml += buffer; + + sXml += _fInterlockedSequential ? + "true\n" : + "false\n"; + } + + sprintf_s(buffer, _countof(buffer), "%I64u\n", _ullThreadStride); + sXml += buffer; + + sprintf_s(buffer, _countof(buffer), "%I64u\n", _ullMaxFileSize); + sXml += buffer; + + sprintf_s(buffer, _countof(buffer), "%u\n", _dwRequestCount); + sXml += buffer; + + sprintf_s(buffer, _countof(buffer), "%u\n", _ulWriteRatio); + sXml += buffer; + + sprintf_s(buffer, _countof(buffer), "%u\n", _dwThroughputBytesPerMillisecond); + sXml += buffer; + + sprintf_s(buffer, _countof(buffer), "%u\n", _dwThreadsPerFile); + sXml += buffer; + + if (_ioPriorityHint == IoPriorityHintVeryLow) + { + sXml += "1\n"; + } + else if (_ioPriorityHint == IoPriorityHintLow) + { + sXml += "2\n"; + } + else if (_ioPriorityHint == IoPriorityHintNormal) + { + sXml += "3\n"; + } + else + { + sXml += "* UNSUPPORTED *\n"; + } + + sXml += "\n"; + + + return sXml; +} + +bool Target::_FillRandomDataWriteBuffer() +{ + assert(_pRandomDataWriteBuffer != nullptr); + bool fOk = true; + size_t cb = static_cast(GetRandomDataWriteBufferSize()); + if (GetRandomDataWriteBufferSourcePath() == "") + { + // fill buffer with random data + for (size_t i = 0; i < cb; i++) + { + _pRandomDataWriteBuffer[i] = (rand() % 256); + } + } + else + { + // fill buffer from file + HANDLE hFile = CreateFile(GetRandomDataWriteBufferSourcePath().c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr); + if (hFile != INVALID_HANDLE_VALUE) + { + UINT64 cbLeftToRead = GetRandomDataWriteBufferSize(); + BYTE *pBuffer = _pRandomDataWriteBuffer; + bool fReadSuccess = true; + while (fReadSuccess && cbLeftToRead > 0) + { + DWORD cbToRead = static_cast(min(64 * 1024, cbLeftToRead)); + DWORD cbRead; + fReadSuccess = ((ReadFile(hFile, pBuffer, cbToRead, &cbRead, nullptr) == TRUE) && (cbRead > 0)); + pBuffer += cbRead; + } + + // if the file is smaller than the buffer, repeat its content + BYTE *pSource = _pRandomDataWriteBuffer; + const BYTE *pPastEnd = pSource + GetRandomDataWriteBufferSize(); + while (pBuffer < pPastEnd) + { + *pBuffer++ = *pSource++; + } + CloseHandle(hFile); + } + else + { + fOk = false; + // TODO: print error message? + } + } + return fOk; +} + +bool Target::AllocateAndFillRandomDataWriteBuffer() +{ + assert(_pRandomDataWriteBuffer == nullptr); + bool fOk = true; + size_t cb = static_cast(GetRandomDataWriteBufferSize()); + assert(cb > 0); + + // TODO: make sure the size if <= max value for size_t + /// CrystalDiskMark 4 does not support Large Page + if (false && GetUseLargePages()) + { + /// size_t cbMinLargePage = GetLargePageMinimum(); + /// size_t cbRoundedSize = (cb + cbMinLargePage - 1) & ~(cbMinLargePage - 1); + /// _pRandomDataWriteBuffer = (BYTE *)VirtualAlloc(nullptr, cbRoundedSize, MEM_COMMIT | MEM_RESERVE | MEM_LARGE_PAGES, PAGE_EXECUTE_READWRITE); + } + else + { + _pRandomDataWriteBuffer = (BYTE *)VirtualAlloc(nullptr, cb, MEM_COMMIT, PAGE_READWRITE); + } + + fOk = (_pRandomDataWriteBuffer != nullptr); + if (fOk) + { + fOk = _FillRandomDataWriteBuffer(); + } + return fOk; +} + +void Target::FreeRandomDataWriteBuffer() +{ + if (nullptr != _pRandomDataWriteBuffer) + { + VirtualFree(_pRandomDataWriteBuffer, 0, MEM_RELEASE); + _pRandomDataWriteBuffer = nullptr; + } +} + +BYTE* Target::GetRandomDataWriteBuffer() +{ + size_t cbBuffer = static_cast(GetRandomDataWriteBufferSize()); + size_t cbBlock = GetBlockSizeInBytes(); + + // leave enough bytes in the buffer for one block + size_t randomOffset = rand() % (cbBuffer - (cbBlock - 1)); + + bool fUnbufferedIO = (GetDisableOSCache() || GetDisableAllCache()); + if (fUnbufferedIO) + { + // for unbuffered IO, offset in the buffer needs to be DWORD-aligned + const size_t cbAlignment = 4; + randomOffset -= (randomOffset % cbAlignment); + } + + BYTE *pBuffer = reinterpret_cast(reinterpret_cast(_pRandomDataWriteBuffer)+randomOffset); + + // unbuffered IO needs aligned addresses + assert(!fUnbufferedIO || (reinterpret_cast(pBuffer) % 4 == 0)); + + assert(pBuffer >= _pRandomDataWriteBuffer); + assert(pBuffer <= _pRandomDataWriteBuffer + GetRandomDataWriteBufferSize() - GetBlockSizeInBytes()); + + return pBuffer; +} + +string TimeSpan::GetXml() const +{ + string sXml("\n"); + char buffer[4096]; + + sXml += _fCompletionRoutines ? "true\n" : "false\n"; + sXml += _fMeasureLatency ? "true\n" : "false\n"; + sXml += _fCalculateIopsStdDev ? "true\n" : "false\n"; + sXml += _fDisableAffinity ? "true\n" : "false\n"; + sXml += _fGroupAffinity ? "true\n" : "false\n"; + + sprintf_s(buffer, _countof(buffer), "%u\n", _ulDuration); + sXml += buffer; + + sprintf_s(buffer, _countof(buffer), "%u\n", _ulWarmUp); + sXml += buffer; + + sprintf_s(buffer, _countof(buffer), "%u\n", _ulCoolDown); + sXml += buffer; + + sprintf_s(buffer, _countof(buffer), "%u\n", _dwThreadCount); + sXml += buffer; + + sprintf_s(buffer, _countof(buffer), "%u\n", _ulIoBucketDurationInMilliseconds); + sXml += buffer; + + sprintf_s(buffer, _countof(buffer), "%u\n", _ulRandSeed); + sXml += buffer; + + if (_vAffinity.size() > 0) + { + sXml += "\n"; + for (auto a : _vAffinity) + { + sprintf_s(buffer, _countof(buffer), "%u\n", a); + sXml += buffer; + } + sXml += "\n"; + } + + sXml += "\n"; + for (const auto& target : _vTargets) + { + sXml += target.GetXml(); + } + sXml += "\n"; + sXml += "\n"; + return sXml; +} + +void TimeSpan::MarkFilesAsPrecreated(const vector vFiles) +{ + for (auto sFile : vFiles) + { + for (auto pTarget = _vTargets.begin(); pTarget != _vTargets.end(); pTarget++) + { + if (sFile == pTarget->GetPath()) + { + pTarget->SetPrecreated(true); + } + } + } +} + +string Profile::GetXml() const +{ + string sXml("\n"); + char buffer[4096]; + + sprintf_s(buffer, _countof(buffer), "%u\n", _dwProgress); + sXml += buffer; + + if (_resultsFormat == ResultsFormat::Text) + { + sXml += "text\n"; + } + else if (_resultsFormat == ResultsFormat::Xml) + { + sXml += "xml\n"; + } + else + { + sXml += "* UNSUPPORTED *\n"; + } + + sXml += _fVerbose ? "true\n" : "false\n"; + if (_precreateFiles == PrecreateFiles::UseMaxSize) + { + sXml += "UseMaxSize\n"; + } + else if (_precreateFiles == PrecreateFiles::OnlyFilesWithConstantSizes) + { + sXml += "CreateOnlyFilesWithConstantSizes\n"; + } + else if (_precreateFiles == PrecreateFiles::OnlyFilesWithConstantOrZeroSizes) + { + sXml += "CreateOnlyFilesWithConstantOrZeroSizes\n"; + } + + if (_fEtwEnabled) + { + sXml += _fEtwProcess ? "true\n" : "false\n"; + sXml += _fEtwThread ? "true\n" : "false\n"; + sXml += _fEtwImageLoad ? "true\n" : "false\n"; + sXml += _fEtwDiskIO ? "true\n" : "false\n"; + sXml += _fEtwMemoryPageFaults ? "true\n" : "false\n"; + sXml += _fEtwMemoryHardFaults ? "true\n" : "false\n"; + sXml += _fEtwNetwork ? "true\n" : "false\n"; + sXml += _fEtwRegistry ? "true\n" : "false\n"; + sXml += _fEtwUsePagedMemory ? "true\n" : "false\n"; + sXml += _fEtwUsePerfTimer ? "true\n" : "false\n"; + sXml += _fEtwUseSystemTimer ? "true\n" : "false\n"; + sXml += _fEtwUseCyclesCounter ? "true\n" : "false\n"; + } + + sXml += "\n"; + for (const auto& timespan : _vTimeSpans) + { + sXml += timespan.GetXml(); + } + sXml += "\n"; + sXml += "\n"; + return sXml; +} + +void Profile::MarkFilesAsPrecreated(const vector vFiles) +{ + for (auto pTimeSpan = _vTimeSpans.begin(); pTimeSpan != _vTimeSpans.end(); pTimeSpan++) + { + pTimeSpan->MarkFilesAsPrecreated(vFiles); + } +} + +bool Profile::Validate(bool fSingleSpec) const +{ + bool fOk = true; + for (const auto& timeSpan : GetTimeSpans()) + { + if (timeSpan.GetDisableAffinity() && timeSpan.GetAffinityAssignments().size() > 0) + { + fprintf(stderr, "ERROR: -n and -a parameters cannot be used together\n"); + fOk = false; + } + + for (const auto& target : timeSpan.GetTargets()) + { + const bool targetHasMultipleThreads = (timeSpan.GetThreadCount() > 1) || (target.GetThreadsPerFile() > 1); + + if (timeSpan.GetThreadCount() > 0 && target.GetThreadsPerFile() > 1) + { + fprintf(stderr, "ERROR: -F and -t parameters cannot be used together\n"); + fOk = false; + } + + if (target.GetDisableAllCache() && target.GetDisableOSCache()) + { + fprintf(stderr, "WARNING: -S is included in the effect of -h, specifying both is not required\n"); + } + + if (target.GetThroughputInBytesPerMillisecond() > 0 && timeSpan.GetCompletionRoutines()) + { + fprintf(stderr, "ERROR: -g throughput control cannot be used with -x completion routines\n"); + fOk = false; + } + + // If burst size is specified think time must be specified and If think time is specified burst size should be non zero + if ((target.GetThinkTime() == 0 && target.GetBurstSize() > 0) || (target.GetThinkTime() > 0 && target.GetBurstSize() == 0)) + { + fprintf(stderr, "ERROR: need to specify -j with -i\n"); + fOk = false; + } + + // FIXME: we can no longer do this check, because the target no longer + // contains a property that uniquely identifies the case where "-s" or + // was passed. +#if 0 + if (target.GetUseRandomAccessPattern() && target.GetEnableCustomStrideSize()) + { + fprintf(stderr, "WARNING: -s is ignored if -r is provided\n"); + } +#endif + if (target.GetUseRandomAccessPattern()) + { + if (target.GetThreadStrideInBytes() > 0) + { + fprintf(stderr, "ERROR: -T conflicts with -r\n"); + fOk = false; + // although ullThreadStride==0 is a valid value, it's interpreted as "not provided" for this warning + } + + if (target.GetUseInterlockedSequential()) + { + fprintf(stderr, "ERROR: -si conflicts with -r\n"); + fOk = false; + } + + if (target.GetUseParallelAsyncIO()) + { + fprintf(stderr, "ERROR: -p conflicts with -r\n"); + fOk = false; + } + } + else + { + if (target.GetUseParallelAsyncIO() && target.GetRequestCount() == 1) + { + fprintf(stderr, "WARNING: -p does not have effect unless outstanding I/O count (-o) is > 1\n"); + } + + if (timeSpan.GetRandSeed() > 0) + { + fprintf(stderr, "WARNING: -z is ignored if -r is not provided\n"); + // although ulRandSeed==0 is a valid value, it's interpreted as "not provided" for this warning + } + + if (target.GetUseInterlockedSequential()) + { + if (target.GetThreadStrideInBytes() > 0) + { + fprintf(stderr, "ERROR: -si conflicts with -T\n"); + fOk = false; + } + + if (target.GetUseParallelAsyncIO()) + { + fprintf(stderr, "ERROR: -si conflicts with -p\n"); + fOk = false; + } + + if (!targetHasMultipleThreads) + { + fprintf(stderr, "WARNING: single-threaded test, -si ignored\n"); + } + } + else + { + if (targetHasMultipleThreads && !target.GetThreadStrideInBytes()) + { + fprintf(stderr, "WARNING: target access pattern will not be sequential, consider -si\n"); + } + + if (!targetHasMultipleThreads && target.GetThreadStrideInBytes()) + { + fprintf(stderr, "ERROR: -T has no effect unless multiple threads per target are used\n"); + fOk = false; + } + } + } + + if (target.GetRandomDataWriteBufferSize() > 0) + { + if (target.GetRandomDataWriteBufferSize() < target.GetBlockSizeInBytes()) + { + fprintf(stderr, "ERROR: custom write buffer (-Z) is smaller than the block size. Write buffer size: %I64u block size: %u\n", + target.GetRandomDataWriteBufferSize(), + target.GetBlockSizeInBytes()); + fOk = false; + } + } + + // in the cases where there is only a single configuration specified for each target (e.g., cmdline), + // currently there are no validations specific to individual targets (e.g., pre-existing files) + // so we can stop validation now. this allows us to only warn/error once, as opposed to repeating + // it for each target. + if (fSingleSpec) + { + break; + } + } + } + return fOk; +} + +bool ThreadParameters::AllocateAndFillBufferForTarget(const Target& target) +{ + bool fOk = true; + BYTE *pDataBuffer = nullptr; + size_t cbDataBuffer = target.GetBlockSizeInBytes() * target.GetRequestCount(); + + /// CrystalDiskMark 4 does not support Large Page + if (false && target.GetUseLargePages()) + { + /// size_t cbMinLargePage = GetLargePageMinimum(); + /// size_t cbRoundedSize = (cbDataBuffer + cbMinLargePage - 1) & ~(cbMinLargePage - 1); + /// pDataBuffer = (BYTE *)VirtualAlloc(nullptr, cbRoundedSize, MEM_COMMIT | MEM_RESERVE | MEM_LARGE_PAGES, PAGE_EXECUTE_READWRITE); + } + else + { + pDataBuffer = (BYTE *)VirtualAlloc(nullptr, cbDataBuffer, MEM_COMMIT, PAGE_READWRITE); + } + + fOk = (pDataBuffer != nullptr); + + //fill buffer (useful only for write tests) + if (fOk && target.GetWriteRatio() > 0) + { + if (target.GetZeroWriteBuffers()) + { + memset(pDataBuffer, 0, cbDataBuffer); + } + else + { + for (size_t i = 0; i < cbDataBuffer; i++) + { + pDataBuffer[i] = (BYTE)(i % 256); + } + } + } + + if (fOk) + { + vpDataBuffers.push_back(pDataBuffer); + } + + return fOk; +} + +BYTE* ThreadParameters::GetReadBuffer(size_t iTarget, size_t iRequest) +{ + return vpDataBuffers[iTarget] + (iRequest * vTargets[iTarget].GetBlockSizeInBytes()); +} + +BYTE* ThreadParameters::GetWriteBuffer(size_t iTarget, size_t iRequest) +{ + BYTE *pBuffer = nullptr; + + Target& target(vTargets[iTarget]); + size_t cb = static_cast(target.GetRandomDataWriteBufferSize()); + if (cb == 0) + { + pBuffer = vpDataBuffers[iTarget] + (iRequest * vTargets[iTarget].GetBlockSizeInBytes()); + } + else + { + pBuffer = target.GetRandomDataWriteBuffer(); + } + return pBuffer; +} + +DWORD ThreadParameters::GetTotalRequestCount() const +{ + DWORD cRequests = 0; + + for (const auto& t : vTargets) + { + cRequests += t.GetRequestCount(); + } + + return cRequests; +} diff --git a/CristalDiskMark/source/diskspd2_0_15a/Common/Common.h b/CristalDiskMark/source/diskspd2_0_15a/Common/Common.h new file mode 100644 index 0000000..c6705b6 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/Common/Common.h @@ -0,0 +1,814 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + +#include +#include +#include //ntdll.dll +#include +#include "Histogram.h" +#include "IoBucketizer.h" + +using namespace std; + +/// structures used for passing the input parameters + +// versioning material. for simplicity in consumption, please ensure that the date string +// parses via the System.Datetime constructor as follows (in Powershell): +// +// [datetime] "string" +// +// this should result in a valid System.Datetime object, rendered like: +// +// Monday, June 16, 2014 12:00:00 AM + +#define DISKSPD_RELEASE_TAG "" +#define DISKSPD_NUMERIC_VERSION_STRING "2.0.15" DISKSPD_RELEASE_TAG +#define DISKSPD_DATE_VERSION_STRING "2015/01/09" + +typedef void (WINAPI *PRINTF)(const char*, va_list); //function used for displaying formatted data (printf style) + +struct ETWEventCounters +{ + UINT64 ullIORead; // Read + UINT64 ullIOWrite; // Write + UINT64 ullMMTransitionFault; // Transition fault + UINT64 ullMMDemandZeroFault; // Demand Zero fault + UINT64 ullMMCopyOnWrite; // Copy on Write + UINT64 ullMMGuardPageFault; // Guard Page fault + UINT64 ullMMHardPageFault; // Hard page fault + UINT64 ullNetTcpSend; // Send + UINT64 ullNetTcpReceive; // Receive + UINT64 ullNetUdpSend; // Send + UINT64 ullNetUdpReceive; // Receive + UINT64 ullNetConnect; // Connect + UINT64 ullNetDisconnect; // Disconnect + UINT64 ullNetRetransmit; // ReTransmit + UINT64 ullNetAccept; // Accept + UINT64 ullNetReconnect; // ReConnect + UINT64 ullRegCreate; // NtCreateKey + UINT64 ullRegOpen; // NtOpenKey + UINT64 ullRegDelete; // NtDeleteKey + UINT64 ullRegQuery; // NtQueryKey + UINT64 ullRegSetValue; // NtSetValueKey + UINT64 ullRegDeleteValue; // NtDeleteValueKey + UINT64 ullRegQueryValue; // NtQueryValueKey + UINT64 ullRegEnumerateKey; // NtEnumerateKey + UINT64 ullRegEnumerateValueKey; // NtEnumerateValueKey + UINT64 ullRegQueryMultipleValue; // NtQueryMultipleValueKey + UINT64 ullRegSetInformation; // NtSetInformationKey + UINT64 ullRegFlush; // NtFlushKey + UINT64 ullRegKcbDmp; // KcbDump/create + UINT64 ullThreadStart; + UINT64 ullThreadEnd; + UINT64 ullProcessStart; + UINT64 ullProcessEnd; + UINT64 ullImageLoad; +}; + + +// structure containing informations about ETW session +struct ETWSessionInfo +{ + ULONG ulBufferSize; + ULONG ulMinimumBuffers; + ULONG ulMaximumBuffers; + ULONG ulFreeBuffers; + ULONG ulBuffersWritten; + ULONG ulFlushTimer; + LONG lAgeLimit; + ULONG ulNumberOfBuffers; + ULONG ulEventsLost; + ULONG ulLogBuffersLost; + ULONG ulRealTimeBuffersLost; +}; + +// structure containing parameters concerning ETW session provided by user +struct ETWMask +{ + BOOL bProcess; + BOOL bThread; + BOOL bImageLoad; + BOOL bDiskIO; + BOOL bMemoryPageFaults; + BOOL bMemoryHardFaults; + BOOL bNetwork; + BOOL bRegistry; + BOOL bUsePagedMemory; + BOOL bUsePerfTimer; + BOOL bUseSystemTimer; + BOOL bUseCyclesCounter; +}; + +namespace UnitTests +{ + class PerfTimerUnitTests; + class ProfileUnitTests; + class TargetUnitTests; +} + +class PerfTimer +{ +public: + + static UINT64 GetTime(); + + static double PerfTimeToMicroseconds(const double); + static double PerfTimeToMilliseconds(const double); + static double PerfTimeToSeconds(const double); + static double PerfTimeToMicroseconds(const UINT64); + static double PerfTimeToMilliseconds(const UINT64); + static double PerfTimeToSeconds(const UINT64); + + static UINT64 MicrosecondsToPerfTime(const double); + static UINT64 MillisecondsToPerfTime(const double); + static UINT64 SecondsToPerfTime(const double); + +private: + + static const UINT64 TIMER_FREQ; + static UINT64 _GetPerfTimerFreq(); + + friend class UnitTests::PerfTimerUnitTests; +}; + +struct PercentileDescriptor +{ + double Percentile; + string Name; +}; + +class Util +{ +public: + static string DoubleToStringHelper(const double); + template static T QuotientCeiling(T dividend, T divisor) + { + return (dividend + divisor - 1) / divisor; + } +}; + +// To keep track of which type of IO was issued +enum class IOOperation +{ + ReadIO = 1, + WriteIO +}; + +class TargetResults +{ +public: + TargetResults() : + ullFileSize(0), + ullBytesCount(0), + ullIOCount(0), + ullReadBytesCount(0), + ullReadIOCount(0), + ullWriteBytesCount(0), + ullWriteIOCount(0) + { + + } + + void Add(DWORD dwBytesTransferred, + IOOperation type, + PUINT64 pullIoStartTime, + PUINT64 pullSpanStartTime, + bool fMeasureLatency, + bool fCalculateIopsStdDev + ) + { + float fDurationMsec = 0; + UINT64 ullEndTime = 0; + // assume it is worthwhile to stay off of the time query path unless needed (micro-overhead) + if (fMeasureLatency || fCalculateIopsStdDev) + { + ullEndTime = PerfTimer::GetTime(); + } + + if (fMeasureLatency) + { + UINT64 ullDuration = ullEndTime - *pullIoStartTime; + fDurationMsec = static_cast(PerfTimer::PerfTimeToMicroseconds(ullDuration)); + + if (type == IOOperation::ReadIO) + { + readLatencyHistogram.Add(fDurationMsec); + } + else + { + writeLatencyHistogram.Add(fDurationMsec); + } + } + + UINT64 ullRelativeCompletionTime = 0; + if (fCalculateIopsStdDev) + { + ullRelativeCompletionTime = ullEndTime - *pullSpanStartTime; + + if (type == IOOperation::ReadIO) + { + readBucketizer.Add(ullRelativeCompletionTime); + } + else + { + writeBucketizer.Add(ullRelativeCompletionTime); + } + } + + if (type == IOOperation::ReadIO) + { + ullReadBytesCount += dwBytesTransferred; // update read bytes counter + ullReadIOCount++; // update completed read I/O operations counter + } + else + { + ullWriteBytesCount += dwBytesTransferred; // update write bytes counter + ullWriteIOCount++; // update completed write I/O operations counter + } + + ullBytesCount += dwBytesTransferred; // update bytes counter + ullIOCount++; // update completed I/O operations counter + } + + string sPath; + UINT64 ullFileSize; //size of the file + UINT64 ullBytesCount; //number of accessed bytes + UINT64 ullIOCount; //number of performed I/O operations + UINT64 ullReadBytesCount; //number of bytes read + UINT64 ullReadIOCount; //number of performed Read I/O operations + UINT64 ullWriteBytesCount; //number of bytes written + UINT64 ullWriteIOCount; //number of performed Write I/O operations + + Histogram readLatencyHistogram; + Histogram writeLatencyHistogram; + + IoBucketizer readBucketizer; + IoBucketizer writeBucketizer; +}; + +class ThreadResults +{ +public: + vector vTargetResults; +}; + +class Results +{ +public: + bool fUseETW; + struct ETWEventCounters EtwEventCounters; + struct ETWMask EtwMask; + struct ETWSessionInfo EtwSessionInfo; + vector vThreadResults; + UINT64 ullTimeCount; + vector vSystemProcessorPerfInfo; +}; + +typedef void (*CALLBACK_TEST_STARTED)(); //callback function to notify that the measured test is about to start +typedef void (*CALLBACK_TEST_FINISHED)(); //callback function to notify that the measured test has just finished + +class SystemInformation +{ +public: + string sComputerName; + + SystemInformation() + { + char buffer[64]; + DWORD cb = _countof(buffer); + BOOL fResult; + +#pragma prefast(suppress:38020, "Yes, we're aware this is an ANSI API in a UNICODE project") + fResult = GetComputerNameExA(ComputerNamePhysicalDnsHostname, buffer, &cb); + if (fResult) + { + sComputerName = buffer; + } + } + + string SystemInformation::GetXml() const + { + string sXml("\n"); + + // identify computer which ran the test + sXml += ""; + sXml += sComputerName; + sXml += "\n"; + + // identify tool version which performed the test + sXml += "\n"; + sXml += "" DISKSPD_NUMERIC_VERSION_STRING "\n"; + sXml += "" DISKSPD_DATE_VERSION_STRING "\n"; + sXml += "\n"; + + sXml += "\n"; + + return sXml; + } +}; + +struct Synchronization +{ + ULONG ulStructSize; //size of the structure that the caller is aware of (to easier achieve backward compatibility in a future) + HANDLE hStopEvent; //an event to be signalled if the scenario is to be stop before time ellapses + HANDLE hStartEvent; //an event for signalling start + CALLBACK_TEST_STARTED pfnCallbackTestStarted; //a function to be called if the measured test is about to start + CALLBACK_TEST_FINISHED pfnCallbackTestFinished; //a function to be called as soon as the measrued test finishes +}; + +#define STRUCT_SYNCHRONIZATION_SUPPORTS(pSynch, Field) ( \ + (NULL != (pSynch)) && \ + ((pSynch)->ulStructSize >= offsetof(struct Synchronization, Field) + sizeof((pSynch)->Field)) \ + ) + +class Target +{ +public: + + Target() : + _dwBlockSize(64 * 1024), + _dwRequestCount(2), + _ullBlockAlignment(64 * 1024), + _fBlockAlignmentValid(false), + _fUseRandomAccessPattern(false), + _ullBaseFileOffset(0), + _fParallelAsyncIO(false), + _fInterlockedSequential(false), + _fDisableOSCache(false), + _fDisableAllCache(false), + _fZeroWriteBuffers(false), + _dwThreadsPerFile(1), + _ullThreadStride(0), + _fCreateFile(false), + _fPrecreated(false), + _ullFileSize(0), + _ullMaxFileSize(0), + _ulWriteRatio(0), + _fUseBurstSize(false), + _dwBurstSize(0), + _dwThinkTime(0), + _fThinkTime(false), + _fSequentialScanHint(false), + _fRandomAccessHint(false), + _fUseLargePages(false), + _ioPriorityHint(IoPriorityHintNormal), + _dwThroughputBytesPerMillisecond(0), + _cbRandomDataWriteBuffer(0), + _sRandomDataWriteBufferSourcePath(), + _pRandomDataWriteBuffer(nullptr) + { + } + + void SetPath(string sPath) { _sPath = sPath; } + string GetPath() const { return _sPath; } + + void SetBlockSizeInBytes(DWORD dwBlockSize) { _dwBlockSize = dwBlockSize; } + DWORD GetBlockSizeInBytes() const { return _dwBlockSize; } + + void SetBlockAlignmentInBytes(UINT64 ullBlockAlignment) + { + _ullBlockAlignment = ullBlockAlignment; + _fBlockAlignmentValid = true; + } + + UINT64 GetBlockAlignmentInBytes() const + { + return _fBlockAlignmentValid ? _ullBlockAlignment : _dwBlockSize; + } + + void SetUseRandomAccessPattern(bool fUseRandomAccessPattern) { _fUseRandomAccessPattern = fUseRandomAccessPattern; } + bool GetUseRandomAccessPattern() const { return _fUseRandomAccessPattern; } + + void SetBaseFileOffsetInBytes(UINT64 ullBaseFileOffset) { _ullBaseFileOffset = ullBaseFileOffset; } + UINT64 GetBaseFileOffsetInBytes() const { return _ullBaseFileOffset; } + + void SetSequentialScanHint(bool fSequentialScanHint) { _fSequentialScanHint = fSequentialScanHint; } + bool GetSequentialScanHint() const { return _fSequentialScanHint; } + + void SetRandomAccessHint(bool fRandomAccessHint) { _fRandomAccessHint = fRandomAccessHint; } + bool GetRandomAccessHint() const { return _fRandomAccessHint; } + + void SetUseLargePages(bool fUseLargePages) { _fUseLargePages = fUseLargePages; } + bool GetUseLargePages() const { return _fUseLargePages; } + + void SetRequestCount(DWORD dwRequestCount) { _dwRequestCount = dwRequestCount; } + DWORD GetRequestCount() const { return _dwRequestCount; } + + void SetDisableOSCache(bool fDisableOSCache) { _fDisableOSCache = fDisableOSCache; } + bool GetDisableOSCache() const { return _fDisableOSCache; } + + void SetDisableAllCache(bool fDisableAllCache) { _fDisableAllCache = fDisableAllCache; } + bool GetDisableAllCache() const { return _fDisableAllCache; } + + void SetZeroWriteBuffers(bool fZeroWriteBuffers) { _fZeroWriteBuffers = fZeroWriteBuffers; } + bool GetZeroWriteBuffers() const { return _fZeroWriteBuffers; } + + void SetRandomDataWriteBufferSize(UINT64 cbWriteBuffer) { _cbRandomDataWriteBuffer = cbWriteBuffer; } + UINT64 GetRandomDataWriteBufferSize(void) const { return _cbRandomDataWriteBuffer; } + + void SetRandomDataWriteBufferSourcePath(string sPath) { _sRandomDataWriteBufferSourcePath = sPath; } + string GetRandomDataWriteBufferSourcePath() const { return _sRandomDataWriteBufferSourcePath; } + + void SetUseBurstSize(bool fUseBurstSize) { _fUseBurstSize = fUseBurstSize; } + bool GetUseBurstSize() const { return _fUseBurstSize; } + + void SetBurstSize(DWORD dwBurstSize) { _dwBurstSize = dwBurstSize; } + DWORD GetBurstSize() const { return _dwBurstSize; } + + void SetThinkTime(DWORD dwThinkTime) { _dwThinkTime = dwThinkTime; } + DWORD GetThinkTime() const { return _dwThinkTime; } + + void SetEnableThinkTime(bool fEnable) { _fThinkTime = fEnable; } + bool GetEnableThinkTime() const { return _fThinkTime; } + + void SetThreadsPerFile(DWORD dwThreadsPerFile) { _dwThreadsPerFile = dwThreadsPerFile; } + DWORD GetThreadsPerFile() const { return _dwThreadsPerFile; } + + void SetCreateFile(bool fCreateFile) { _fCreateFile = fCreateFile; } + bool GetCreateFile() const { return _fCreateFile; } + + void SetFileSize(UINT64 ullFileSize) { _ullFileSize = ullFileSize; } + UINT64 GetFileSize() const { return _ullFileSize; } // TODO: InBytes + + void SetMaxFileSize(UINT64 ullMaxFileSize) { _ullMaxFileSize = ullMaxFileSize; } + UINT64 GetMaxFileSize() const { return _ullMaxFileSize; } + + void SetWriteRatio(UINT32 ulWriteRatio) { _ulWriteRatio = ulWriteRatio; } + UINT32 GetWriteRatio() const { return _ulWriteRatio; } + + void SetUseParallelAsyncIO(bool fParallelAsyncIO) { _fParallelAsyncIO = fParallelAsyncIO; } + bool GetUseParallelAsyncIO() const { return _fParallelAsyncIO; } + + void SetUseInterlockedSequential(bool fInterlockedSequential) { _fInterlockedSequential = fInterlockedSequential; } + bool GetUseInterlockedSequential() const { return _fInterlockedSequential; } + + void SetThreadStrideInBytes(UINT64 ullThreadStride) { _ullThreadStride = ullThreadStride; } + UINT64 GetThreadStrideInBytes() const { return _ullThreadStride; } + + void SetIOPriorityHint(PRIORITY_HINT _hint) + { + assert(_hint < MaximumIoPriorityHintType); + _ioPriorityHint = _hint; + } + PRIORITY_HINT GetIOPriorityHint() const { return _ioPriorityHint; } + + void SetPrecreated(bool fPrecreated) { _fPrecreated = fPrecreated; } + bool GetPrecreated() const { return _fPrecreated; } + + void SetThroughput(DWORD dwThroughputBytesPerMillisecond) { _dwThroughputBytesPerMillisecond = dwThroughputBytesPerMillisecond; } + DWORD GetThroughputInBytesPerMillisecond() const { return _dwThroughputBytesPerMillisecond; } + + string GetXml() const; + + bool AllocateAndFillRandomDataWriteBuffer(); + void FreeRandomDataWriteBuffer(); + BYTE* GetRandomDataWriteBuffer(); + +private: + string _sPath; + DWORD _dwBlockSize; + DWORD _dwRequestCount; // TODO: change the name to something more descriptive (OutstandingRequestCount?) + + UINT64 _ullBlockAlignment; + bool _fBlockAlignmentValid; + bool _fUseRandomAccessPattern; + + UINT64 _ullBaseFileOffset; + bool _fParallelAsyncIO; + bool _fInterlockedSequential; + bool _fDisableOSCache; + bool _fDisableAllCache; + bool _fZeroWriteBuffers; + DWORD _dwThreadsPerFile; + UINT64 _ullThreadStride; + + bool _fCreateFile; + bool _fPrecreated; // used to track which files have been created before the first timespan and which have to be created later + UINT64 _ullFileSize; + UINT64 _ullMaxFileSize; + UINT32 _ulWriteRatio; + bool _fUseBurstSize; // TODO: "use" or "enable"?; since burst size must be specified with the think time, one variable should be sufficient + DWORD _dwBurstSize; // number of IOs in a burst + DWORD _dwThinkTime; // time to pause before issuing the next burst of IOs + // TODO: could this be removed by using _dwThinkTime==0? + bool _fThinkTime; //variable to decide whether to think between IOs (default is false) + DWORD _dwThroughputBytesPerMillisecond; // set to 0 to disable throttling + + bool _fSequentialScanHint; // open file with the FILE_FLAG_SEQUENTIAL_SCAN hint + bool _fRandomAccessHint; // open file with the FILE_FLAG_RANDOM_ACCESS hint + bool _fUseLargePages; // Use large pages for IO buffers + + UINT64 _cbRandomDataWriteBuffer; // if > 0, then the write buffer should be filled with random data + string _sRandomDataWriteBufferSourcePath; // file that should be used for filling the write buffer (if the path is not available, use a crypto provider) + BYTE *_pRandomDataWriteBuffer; // a buffer used for write data when _cbWriteBuffer > 0; it's shared by all the threads working on this target + + PRIORITY_HINT _ioPriorityHint; + + bool _FillRandomDataWriteBuffer(); + + friend class UnitTests::ProfileUnitTests; + friend class UnitTests::TargetUnitTests; +}; + +class TimeSpan +{ +public: + TimeSpan() : + _ulDuration(10), + _ulWarmUp(5), + _ulCoolDown(0), + _ulRandSeed(0), + _dwThreadCount(0), + _fGroupAffinity(false), + _fDisableAffinity(false), + _fCompletionRoutines(false), + _fMeasureLatency(false), + _fCalculateIopsStdDev(false), + _ulIoBucketDurationInMilliseconds(1000) + { + } + + void AddAffinityAssignment(UINT32 ulAffinity) + { + _vAffinity.push_back(ulAffinity); + } + vector GetAffinityAssignments() const { return _vAffinity; } + + void AddTarget(const Target& target) + { + _vTargets.push_back(Target(target)); + } + vector GetTargets() const { return _vTargets; } + + void SetDuration(UINT32 ulDuration) { _ulDuration = ulDuration; } + UINT32 GetDuration() const { return _ulDuration; } + + void SetWarmup(UINT32 ulWarmup) { _ulWarmUp = ulWarmup; } + UINT32 GetWarmup() const { return _ulWarmUp; } + + void SetCooldown(UINT32 ulCooldown) { _ulCoolDown = ulCooldown; } + UINT32 GetCooldown() const { return _ulCoolDown; } + + void SetRandSeed(UINT32 ulRandSeed) { _ulRandSeed = ulRandSeed; } + UINT32 GetRandSeed() const { return _ulRandSeed; } + + void SetThreadCount(DWORD dwThreadCount) { _dwThreadCount = dwThreadCount; } + DWORD GetThreadCount() const { return _dwThreadCount; } + + void SetGroupAffinity(bool fGroupAffinity) { _fGroupAffinity = fGroupAffinity; } + bool GetGroupAffinity() const { return _fGroupAffinity; } + + void SetDisableAffinity(bool fDisableAffinity) { _fDisableAffinity = fDisableAffinity; } + bool GetDisableAffinity() const { return _fDisableAffinity; } + + void SetCompletionRoutines(bool fCompletionRoutines) { _fCompletionRoutines = fCompletionRoutines; } + bool GetCompletionRoutines() const { return _fCompletionRoutines; } + + void SetMeasureLatency(bool fMeasureLatency) { _fMeasureLatency = fMeasureLatency; } + bool GetMeasureLatency() const { return _fMeasureLatency; } + + void SetCalculateIopsStdDev(bool fCalculateStdDev) { _fCalculateIopsStdDev = fCalculateStdDev; } + bool GetCalculateIopsStdDev() const { return _fCalculateIopsStdDev; } + + void SetIoBucketDurationInMilliseconds(UINT32 ulIoBucketDurationInMilliseconds) { _ulIoBucketDurationInMilliseconds = ulIoBucketDurationInMilliseconds; } + UINT32 GetIoBucketDurationInMilliseconds() const { return _ulIoBucketDurationInMilliseconds; } + + string GetXml() const; + void MarkFilesAsPrecreated(const vector vFiles); + +private: + vector _vTargets; + UINT32 _ulDuration; + UINT32 _ulWarmUp; + UINT32 _ulCoolDown; + UINT32 _ulRandSeed; + DWORD _dwThreadCount; + bool _fGroupAffinity; + bool _fDisableAffinity; + vector _vAffinity; + bool _fCompletionRoutines; + bool _fMeasureLatency; + bool _fCalculateIopsStdDev; + UINT32 _ulIoBucketDurationInMilliseconds; + + friend class UnitTests::ProfileUnitTests; +}; + +enum class ResultsFormat +{ + Text, + Xml +}; + +enum class PrecreateFiles +{ + None, + UseMaxSize, + OnlyFilesWithConstantSizes, + OnlyFilesWithConstantOrZeroSizes +}; + +class Profile +{ +public: + Profile() : + _fVerbose(false), + _dwProgress(0), + _fEtwEnabled(false), + _fEtwProcess(false), + _fEtwThread(false), + _fEtwImageLoad(false), + _fEtwDiskIO(false), + _fEtwMemoryPageFaults(false), + _fEtwMemoryHardFaults(false), + _fEtwNetwork(false), + _fEtwRegistry(false), + _fEtwUsePagedMemory(false), + _fEtwUsePerfTimer(false), + _fEtwUseSystemTimer(false), + _fEtwUseCyclesCounter(false), + _resultsFormat(ResultsFormat::Text), + _precreateFiles(PrecreateFiles::None) + { + } + + void AddTimeSpan(const TimeSpan& timeSpan) + { + _vTimeSpans.push_back(TimeSpan(timeSpan)); + } + + const vector& GetTimeSpans() const { return _vTimeSpans; } + + void SetVerbose(bool fVerbose) { _fVerbose = fVerbose; } + bool GetVerbose() const { return _fVerbose; } + + void SetProgress(DWORD dwProgress) { _dwProgress = dwProgress; } + DWORD GetProgress() const { return _dwProgress; } + + void SetCmdLine(string sCmdLine) { _sCmdLine = sCmdLine; } + string GetCmdLine() const { return _sCmdLine; }; + + void SetResultsFormat(ResultsFormat format) { _resultsFormat = format; } + ResultsFormat GetResultsFormat() const { return _resultsFormat; } + + void SetPrecreateFiles(PrecreateFiles c) { _precreateFiles = c; } + PrecreateFiles GetPrecreateFiles() const { return _precreateFiles; } + + //ETW + void SetEtwEnabled(bool fEtwEnabled) { _fEtwEnabled = fEtwEnabled; } + void SetEtwProcess(bool fEtwProcess) { _fEtwProcess = fEtwProcess; } + void SetEtwThread(bool fEtwThread) { _fEtwThread = fEtwThread; } + void SetEtwImageLoad(bool fEtwImageLoad) { _fEtwImageLoad = fEtwImageLoad; } + void SetEtwDiskIO(bool fEtwDiskIO) { _fEtwDiskIO = fEtwDiskIO; } + void SetEtwMemoryPageFaults(bool fEtwMemoryPageFaults) { _fEtwMemoryPageFaults = fEtwMemoryPageFaults; } + void SetEtwMemoryHardFaults(bool fEtwMemoryHardFaults) { _fEtwMemoryHardFaults = fEtwMemoryHardFaults; } + void SetEtwNetwork(bool fEtwNetwork) { _fEtwNetwork = fEtwNetwork; } + void SetEtwRegistry(bool fEtwRegistry) { _fEtwRegistry = fEtwRegistry; } + void SetEtwUsePagedMemory(bool fEtwUsePagedMemory) { _fEtwUsePagedMemory = fEtwUsePagedMemory; } + void SetEtwUsePerfTimer(bool fEtwUsePerfTimer) { _fEtwUsePerfTimer = fEtwUsePerfTimer; } + void SetEtwUseSystemTimer(bool fEtwUseSystemTimer) { _fEtwUseSystemTimer = fEtwUseSystemTimer; } + void SetEtwUseCyclesCounter(bool fEtwUseCyclesCounter) { _fEtwUseCyclesCounter = fEtwUseCyclesCounter; } + + bool GetEtwEnabled() const { return _fEtwEnabled; } + bool GetEtwProcess() const { return _fEtwProcess; } + bool GetEtwThread() const { return _fEtwThread; } + bool GetEtwImageLoad() const { return _fEtwImageLoad; } + bool GetEtwDiskIO() const { return _fEtwDiskIO; } + bool GetEtwMemoryPageFaults() const { return _fEtwMemoryPageFaults; } + bool GetEtwMemoryHardFaults() const { return _fEtwMemoryHardFaults; } + bool GetEtwNetwork() const { return _fEtwNetwork; } + bool GetEtwRegistry() const { return _fEtwRegistry; } + bool GetEtwUsePagedMemory() const { return _fEtwUsePagedMemory; } + bool GetEtwUsePerfTimer() const { return _fEtwUsePerfTimer; } + bool GetEtwUseSystemTimer() const { return _fEtwUseSystemTimer; } + bool GetEtwUseCyclesCounter() const { return _fEtwUseCyclesCounter; } + + string GetXml() const; + bool Validate(bool fSingleSpec) const; + void MarkFilesAsPrecreated(const vector vFiles); + +private: + Profile(const Profile& T); + + vector_vTimeSpans; + bool _fVerbose; + DWORD _dwProgress; + string _sCmdLine; + ResultsFormat _resultsFormat; + PrecreateFiles _precreateFiles; + + //ETW + bool _fEtwEnabled; + bool _fEtwProcess; + bool _fEtwThread; + bool _fEtwImageLoad; + bool _fEtwDiskIO; + bool _fEtwMemoryPageFaults; + bool _fEtwMemoryHardFaults; + bool _fEtwNetwork; + bool _fEtwRegistry; + bool _fEtwUsePagedMemory; + bool _fEtwUsePerfTimer; + bool _fEtwUseSystemTimer; + bool _fEtwUseCyclesCounter; + + friend class UnitTests::ProfileUnitTests; +}; + +class ThreadParameters +{ +public: + ThreadParameters() : + pProfile(nullptr), + pTimeSpan(nullptr), + pullSharedSequentialOffsets(nullptr), + ulRandSeed(0), + ulThreadNo(0), + ulRelativeThreadNo(0) + { + } + + const Profile *pProfile; + const TimeSpan *pTimeSpan; + + vector vTargets; + vector vhTargets; + vector vullFileSizes; + vector vpDataBuffers; + vector vOverlapped; // each target has RequestCount OVERLAPPED structures + vector vOverlappedIdToTargetId; + vector vFirstOverlappedIdForTargetId; //id of the first overlapped structure in the vOverlapped vector by target + vector vdwIoType; //as many as vOverlapped; used by the completion routines + vector vIoStartTimes; + + // For vanilla sequential access (-s): + // Private per-thread offsets, incremented directly, indexed to number of targets + vector vullPrivateSequentialOffsets; + + // For interlocked sequential access (-si): + // Pointers to offsets shared between threads, incremented with an interlocked op + UINT64* pullSharedSequentialOffsets; + + UINT32 ulRandSeed; + UINT32 ulThreadNo; + UINT32 ulRelativeThreadNo; + + // accounting + volatile bool *pfAccountingOn; + PUINT64 pullStartTime; + ThreadResults *pResults; + + //group affinity + WORD wGroupNum; + DWORD dwProcNum; + GROUP_AFFINITY GroupAffinity; + + HANDLE hStartEvent; + + // TODO: check how it's used + HANDLE hEndEvent; //used only in case of completion routines (not for IO Completion Ports) + + bool AllocateAndFillBufferForTarget(const Target& target); + BYTE* GetReadBuffer(size_t iTarget, size_t iRequest); + BYTE* GetWriteBuffer(size_t iTarget, size_t iRequest); + DWORD GetTotalRequestCount() const; + +private: + ThreadParameters(const ThreadParameters& T); +}; + +class IResultParser +{ +public: + virtual string ParseResults(Profile& profile, const SystemInformation& system, vector vResults) = 0; + virtual int GetTotalScore() = 0; + virtual double GetAverageLatency() = 0; +}; diff --git a/CristalDiskMark/source/diskspd2_0_15a/Common/Histogram.h b/CristalDiskMark/source/diskspd2_0_15a/Common/Histogram.h new file mode 100644 index 0000000..ee0a312 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/Common/Histogram.h @@ -0,0 +1,273 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#pragma push_macro("min") +#pragma push_macro("max") +#undef min +#undef max + +template +class Histogram +{ + private: + + unsigned _samples; + +#define USE_HASH_TABLE +#ifdef USE_HASH_TABLE + std::unordered_map _data; + + std::map _GetSortedData() const + { + return std::map(_data.begin(), _data.end()); + } +#else + std::map _data; + + std::map _GetSortedData() const + { + return _data; + } +#endif + public: + + Histogram() + : _samples(0) + {} + + void Clear() + { + _data.clear(); + _samples = 0; + } + + void Add(T v) + { + _data[ v ]++; + _samples++; + } + + void Merge(const Histogram &other) + { + for (auto i : other._data) + { + _data[ i.first ] += i.second; + } + + _samples += other._samples; + } + + T GetMin() const + { + T min(std::numeric_limits::max()); + + for (auto i : _data) + { + if (i.first < min) + { + min = i.first; + } + } + + return min; + } + + T GetMax() const + { + T max(std::numeric_limits::min()); + + for (auto i : _data) + { + if (i.first > max) + { + max = i.first; + } + } + + return max; + } + + unsigned GetSampleSize() const + { + return _samples; + } + + T GetPercentile(double p) const + { + // ISSUE-REVIEW + // What do the 0th and 100th percentile really mean? + if ((p < 0) || (p > 1)) + { + throw std::invalid_argument("Percentile must be >= 0 and <= 1"); + } + + const double target = GetSampleSize() * p; + + unsigned cur = 0; + for (auto i : _GetSortedData()) + { + cur += i.second; + if (cur >= target) + { + return i.first; + } + } + + throw std::runtime_error("Percentile is undefined"); + } + + T GetPercentile(int p) const + { + return GetPercentile(static_cast(p)/100); + } + + T GetMedian() const + { + return GetPercentile(0.5); + } + + double GetStdDev() const { return GetStandardDeviation(); } + double GetAvg() const { return GetMean(); } + + double GetMean() const + { + double sum(0); + unsigned samples = GetSampleSize(); + + for (auto i : _data) + { + double bucket_val = + static_cast(i.first) * i.second / samples; + + if (sum + bucket_val < 0) + { + throw std::overflow_error("while trying to accumulate sum"); + } + + sum += bucket_val; + } + + return sum; + } + + double GetStandardDeviation() const + { + double mean(GetMean()); + double ssd(0); + + for (auto i : _data) + { + double dev = static_cast(i.first) - mean; + double sqdev = dev*dev; + ssd += i.second * sqdev; + } + + return sqrt(ssd / GetSampleSize()); + } + + std::string GetHistogramCsv(const unsigned bins) const + { + return GetHistogramCsv(bins, GetMin(), GetMax()); + } + + std::string GetHistogramCsv(const unsigned bins, const T LOW, const T HIGH) const + { + // ISSUE-REVIEW + // Currently bins are defined as strictly less-than + // their upper limit, with the exception of the last + // bin. Otherwise where would I put the max value? + const double binSize = static_cast((HIGH - LOW) / bins); + double limit = static_cast(LOW); + + std::ostringstream os; + os.precision(std::numeric_limits::digits10); + + std::map sortedData = _GetSortedData(); + + auto pos = sortedData.begin(); + + unsigned cumulative = 0; + + for (unsigned bin = 1; bin <= bins; ++bin) + { + unsigned count = 0; + limit += binSize; + + while (pos != sortedData.end() && + (pos->first < limit || bin == bins)) + { + count += pos->second; + ++pos; + } + + cumulative += count; + + os << limit << "," << count << "," << cumulative << std::endl; + } + + return os.str(); + } + + std::string GetRawCsv() const + { + std::ostringstream os; + os.precision(std::numeric_limits::digits10); + + for (auto i : _GetSortedData()) + { + os << i.first << "," << i.second << std::endl; + } + + return os.str(); + } + + std::string GetRaw() const + { + std::ostringstream os; + + for (auto i : _GetSortedData()) + { + os << i.second << " " << i.first << std::endl; + } + + return os.str(); + } +}; + +#pragma pop_macro("min") +#pragma pop_macro("max") \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/Common/IoBucketizer.cpp b/CristalDiskMark/source/diskspd2_0_15a/Common/IoBucketizer.cpp new file mode 100644 index 0000000..8be6306 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/Common/IoBucketizer.cpp @@ -0,0 +1,143 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "IoBucketizer.h" + +const unsigned __int64 INVALID_BUCKET_DURATION = 0; + +IoBucketizer::IoBucketizer() + : _bucketDuration(INVALID_BUCKET_DURATION), + _validBuckets(0) +{} + +void IoBucketizer::Initialize(unsigned __int64 bucketDuration, size_t validBuckets) +{ + if (_bucketDuration != INVALID_BUCKET_DURATION) + { + throw std::runtime_error("IoBucketizer has already been initialized"); + } + if (bucketDuration == INVALID_BUCKET_DURATION) + { + throw std::invalid_argument("Bucket duration must be a positive integer"); + } + + _bucketDuration = bucketDuration; + _validBuckets = validBuckets; + _vBuckets.reserve(_validBuckets); +} + +void IoBucketizer::Add(unsigned __int64 ioCompletionTime) +{ + if (_bucketDuration == INVALID_BUCKET_DURATION) + { + throw std::runtime_error("IoBucketizer has not been initialized"); + } + + size_t bucketNumber = static_cast(ioCompletionTime / _bucketDuration); + size_t currentSize = _vBuckets.size(); + if (currentSize < bucketNumber + 1) + { + _vBuckets.resize(bucketNumber + 1); + // Zero the new entries. Note that size is 1-based and bucketNumber is 0-based. + for (size_t i = currentSize; i <= bucketNumber; i++) + { + _vBuckets[i] = 0; + } + } + _vBuckets[bucketNumber]++; +} + +size_t IoBucketizer::GetNumberOfValidBuckets() const +{ + // Buckets beyond this may exist since Add is willing to extend the vector + // beyond the expected number of valid buckets, but they are not comparable + // buckets (straggling IOs over the timespan boundary). + return (_vBuckets.size() > _validBuckets ? _validBuckets : _vBuckets.size()); +} + +size_t IoBucketizer::GetNumberOfBuckets() const +{ + return _vBuckets.size(); +} + +unsigned int IoBucketizer::GetIoBucket(size_t bucketNumber) const +{ + return _vBuckets[bucketNumber]; +} + +double IoBucketizer::_GetMean() const +{ + size_t numBuckets = GetNumberOfValidBuckets(); + double sum = 0; + + for (size_t i = 0; i < numBuckets; i++) + { + sum += static_cast(_vBuckets[i]) / numBuckets; + } + + return sum; +} + +double IoBucketizer::GetStandardDeviation() const +{ + size_t numBuckets = GetNumberOfValidBuckets(); + + if(numBuckets == 0) + { + return 0.0; + } + + double mean = _GetMean(); + double ssd = 0; + + for (size_t i = 0; i < numBuckets; i++) + { + double dev = static_cast(_vBuckets[i]) - mean; + double sqdev = dev*dev; + ssd += sqdev; + } + + return sqrt(ssd / numBuckets); +} + +void IoBucketizer::Merge(const IoBucketizer& other) +{ + if(other._vBuckets.size() > _vBuckets.size()) + { + _vBuckets.resize(other._vBuckets.size()); + } + if (other._validBuckets > _validBuckets) + { + _validBuckets = other._validBuckets; + } + for(size_t i = 0; i < other._vBuckets.size(); i++) + { + _vBuckets[i] += other.GetIoBucket(i); + } +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/Common/IoBucketizer.h b/CristalDiskMark/source/diskspd2_0_15a/Common/IoBucketizer.h new file mode 100644 index 0000000..512c15d --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/Common/IoBucketizer.h @@ -0,0 +1,52 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + +#include + +class IoBucketizer +{ +public: + IoBucketizer(); + void Initialize(unsigned __int64 bucketDuration, size_t validBuckets); + + size_t GetNumberOfValidBuckets() const; + size_t GetNumberOfBuckets() const; + unsigned int GetIoBucket(size_t bucketNumber) const; + void Add(unsigned __int64 ioCompletionTime); + double GetStandardDeviation() const; + void Merge(const IoBucketizer& other); +private: + double _GetMean() const; + + unsigned __int64 _bucketDuration; + size_t _validBuckets; + std::vector _vBuckets; +}; \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/DiskSpd_Documentation.docx b/CristalDiskMark/source/diskspd2_0_15a/DiskSpd_Documentation.docx new file mode 100644 index 0000000..f0c963b Binary files /dev/null and b/CristalDiskMark/source/diskspd2_0_15a/DiskSpd_Documentation.docx differ diff --git a/CristalDiskMark/source/diskspd2_0_15a/DiskSpd_Documentation.pdf b/CristalDiskMark/source/diskspd2_0_15a/DiskSpd_Documentation.pdf new file mode 100644 index 0000000..a3105a6 Binary files /dev/null and b/CristalDiskMark/source/diskspd2_0_15a/DiskSpd_Documentation.pdf differ diff --git a/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/IORequestGenerator.cpp b/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/IORequestGenerator.cpp new file mode 100644 index 0000000..aa5d12c --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/IORequestGenerator.cpp @@ -0,0 +1,2771 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +// IORequestGenerator.cpp : Defines the entry point for the DLL application. +// + +//FUTURE EXTENSION: make it compile with /W4 + +#ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0600 +#endif + +#ifndef _WIN32_IE + #define _WIN32_IE 0x0500 +#endif + +#include "common.h" +#include "IORequestGenerator.h" + +#include +#include +#include //DISK_GEOMETRY +#include +#include + +#include //WNODE_HEADER + +#include "etw.h" +#include +#include +#include "ThroughputMeter.h" +#include "OverlappedQueue.h" + +/*****************************************************************************/ +// gets partition size, return zero on failure +// +UINT64 GetPartitionSize(HANDLE hFile) +{ + assert(NULL != hFile && INVALID_HANDLE_VALUE != hFile); + + PARTITION_INFORMATION pinf; + OVERLAPPED ovlp = {}; + + ovlp.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (ovlp.hEvent == nullptr) + { + PrintError("ERROR: Failed to create event (error code: %u)\n", GetLastError()); + return 0; + } + + DWORD rbcnt = 0; + DWORD status = ERROR_SUCCESS; + BOOL rslt; + + rslt = DeviceIoControl(hFile, + IOCTL_DISK_GET_PARTITION_INFO, + NULL, + 0, + &pinf, + sizeof(pinf), + &rbcnt, + &ovlp); + + if (!rslt) + { + status = GetLastError(); + if (status == ERROR_IO_PENDING) + { + if (WAIT_OBJECT_0 != WaitForSingleObject(ovlp.hEvent, INFINITE)) + { + PrintError("ERROR: Failed while waiting for event to be signaled (error code: %u)\n", GetLastError()); + } + else + { + rslt = TRUE; + } + } + else + { + PrintError("ERROR: Could not obtain partition info (error code: %u)\n", status); + } + } + + CloseHandle(ovlp.hEvent); + + if (!rslt) + { + return 0; + } + + return pinf.PartitionLength.QuadPart; +} + +/*****************************************************************************/ +// gets physical drive size, return zero on failure +// +UINT64 GetPhysicalDriveSize(HANDLE hFile) +{ + assert(NULL != hFile && INVALID_HANDLE_VALUE != hFile); + + DISK_GEOMETRY geom; + OVERLAPPED ovlp = {}; + + ovlp.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (ovlp.hEvent == nullptr) + { + PrintError("ERROR: Failed to create event (error code: %u)\n", GetLastError()); + return 0; + } + + DWORD rbcnt = 0; + DWORD status = ERROR_SUCCESS; + BOOL rslt; + + rslt = DeviceIoControl(hFile, + IOCTL_DISK_GET_DRIVE_GEOMETRY, + NULL, + 0, + &geom, + sizeof(geom), + &rbcnt, + &ovlp); + + if (!rslt) + { + status = GetLastError(); + if (status == ERROR_IO_PENDING) + { + if (WAIT_OBJECT_0 != WaitForSingleObject(ovlp.hEvent, INFINITE)) + { + PrintError("ERROR: Failed while waiting for event to be signaled (error code: %u)\n", GetLastError()); + } + else + { + rslt = TRUE; + } + } + else + { + PrintError("ERROR: Could not obtain drive geometry (error code: %u)\n", status); + } + } + + CloseHandle(ovlp.hEvent); + + if (!rslt) + { + return 0; + } + + return (UINT64)geom.BytesPerSector * + (UINT64)geom.SectorsPerTrack * + (UINT64)geom.TracksPerCylinder * + (UINT64)geom.Cylinders.QuadPart; +} + +/*****************************************************************************/ +// activates specified privilege in process token +// +bool SetPrivilege(LPCSTR pszPrivilege) +{ + TOKEN_PRIVILEGES TokenPriv; + HANDLE hToken = INVALID_HANDLE_VALUE; + DWORD dwError; + bool fOk = true; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) + { + PrintError("Error opening process token (error code: %u)\n", GetLastError()); + fOk = false; + goto cleanup; + } + + TokenPriv.PrivilegeCount = 1; + TokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + if (!LookupPrivilegeValue(nullptr, pszPrivilege, &TokenPriv.Privileges[0].Luid)) + { + PrintError("Error looking up privilege value %s (error code: %u)\n", pszPrivilege, GetLastError()); + fOk = false; + goto cleanup; + } + + if (!AdjustTokenPrivileges(hToken, FALSE, &TokenPriv, 0, nullptr, nullptr)) + { + PrintError("Error adjusting token privileges for %s (error code: %u)\n", pszPrivilege, GetLastError()); + fOk = false; + goto cleanup; + } + + if (ERROR_SUCCESS != (dwError = GetLastError())) + { + PrintError("Error adjusting token privileges for %s (error code: %u)\n", pszPrivilege, dwError); + fOk = false; + goto cleanup; + } + +cleanup: + if (hToken != INVALID_HANDLE_VALUE) + { + CloseHandle(hToken); + } + + return fOk; +} + +/*****************************************************************************/ +// structures and global variables +// +struct ETWEventCounters g_EtwEventCounters; + +__declspec(align(4)) static LONG volatile g_lRunningThreadsCount = 0; //must be aligned on a 32-bit boundary, otherwise InterlockedIncrement + //and InterlockedDecrement will fail on 64-bit systems + +static ULONG volatile g_ulProcCount = 0; //number of CPUs present in the system +static BOOL volatile g_bRun; //used for letting threads know that they should stop working + +typedef NTSTATUS (__stdcall *NtQuerySysInfo)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG); +static NtQuerySysInfo g_pfnNtQuerySysInfo; + +static PRINTF g_pfnPrintOut = nullptr; +static PRINTF g_pfnPrintError = nullptr; +static PRINTF g_pfnPrintVerbose = nullptr; + +static BOOL volatile g_bThreadError = FALSE; //true means that an error has occured in one of the threads +BOOL volatile g_bTracing = TRUE; //true means that ETW is turned on + +// TODO: is this still needed? +__declspec(align(4)) static LONG volatile g_lGeneratorRunning = 0; //used to detect if GenerateRequests is already running + +static BOOL volatile g_bError = FALSE; //true means there was fatal error during intialization and threads shouldn't perform their work + +/*****************************************************************************/ +//structure and functions to get system groups and processors information +#define MAXIMUM_GROUPS_LARGE 32 + +typedef struct { + WORD wActiveGroupCount; //number of groups in the system + DWORD dwaActiveProcsCount[MAXIMUM_GROUPS_LARGE]; //number of processors per group +} ACTIVE_GROUPS_AND_PROCS, *PACTIVE_GROUPS_AND_PROCS; + +PACTIVE_GROUPS_AND_PROCS g_pActiveGroupsAndProcs; + +//Win7 kernel32.dll groups and processors exported functions +#define GET_ACTIVE_PROCESSOR_GROUP_COUNT ("GetActiveProcessorGroupCount") +#define GET_ACTIVE_PROCESSOR_COUNT ("GetActiveProcessorCount") +#define SET_THREAD_GROUP_AFFINITY ("SetThreadGroupAffinity") + +typedef WORD (WINAPI *PFN_GET_ACTIVE_PROCESSOR_GROUP_COUNT) (VOID); +typedef DWORD (WINAPI *PFN_GET_ACTIVE_PROCESSOR_COUNT) (WORD GroupNumber); +typedef DWORD (WINAPI *PFN_SET_THREAD_GROUP_AFFINITY) (HANDLE hThread, const GROUP_AFFINITY * pGroupAffinity, PGROUP_AFFINITY PreviousGroupAffinity); + +// for XP/2003 support +#define SET_FILE_INFORMATION_BY_HANDLE ("SetFileInformationByHandle") +typedef BOOL(WINAPI *NT6_SET_FILE_INFORMATION_BY_HANDLE) (HANDLE hFile, FILE_INFO_BY_HANDLE_CLASS FileInformationClass, LPVOID lpFileInformation, DWORD dwBufferSize); + +bool IORequestGenerator::_GetActiveGroupsAndProcs() const +{ + HMODULE kernel32; + PFN_GET_ACTIVE_PROCESSOR_GROUP_COUNT GetActiveProcessorGroupCount; + PFN_GET_ACTIVE_PROCESSOR_COUNT GetActiveProcessorCount; + WORD wActiveGroupCtr = 0; + SYSTEM_INFO SystemInfo; + + //load kernel32.dll + kernel32 = LoadLibraryExW(L"kernel32.dll", NULL, 0); + if (kernel32 == NULL) + { + PrintError("ERROR: kernel32.dll library failed to load!\r\n"); + return false; + } + + //get function address from kernel32.dll for groups + GetActiveProcessorGroupCount = (PFN_GET_ACTIVE_PROCESSOR_GROUP_COUNT)GetProcAddress(kernel32, GET_ACTIVE_PROCESSOR_GROUP_COUNT); + + if (GetActiveProcessorGroupCount != NULL) + { + g_pActiveGroupsAndProcs->wActiveGroupCount = GetActiveProcessorGroupCount(); + + //verify that group count number is not bigger than maximume groups supported by the OS + if (g_pActiveGroupsAndProcs->wActiveGroupCount > MAXIMUM_GROUPS_LARGE) + { + PrintError("ERROR: pActiveGroupsAndProcs->wActiveGroupCount = %d\r\n", g_pActiveGroupsAndProcs->wActiveGroupCount); + PrintError("ERROR: Incorrect value; there can be max %d groups in the system\r\n", MAXIMUM_GROUPS_LARGE); + return false; + } + + //get function address from kernel32.dll for processors per group + GetActiveProcessorCount = (PFN_GET_ACTIVE_PROCESSOR_COUNT)GetProcAddress(kernel32, GET_ACTIVE_PROCESSOR_COUNT); + + if (GetActiveProcessorCount != NULL) + { + g_ulProcCount = 0; + + //get number of processors per group + for (wActiveGroupCtr = 0; wActiveGroupCtr < g_pActiveGroupsAndProcs->wActiveGroupCount; wActiveGroupCtr++) + { + g_pActiveGroupsAndProcs->dwaActiveProcsCount[wActiveGroupCtr] = + GetActiveProcessorCount(wActiveGroupCtr); + g_ulProcCount += g_pActiveGroupsAndProcs->dwaActiveProcsCount[wActiveGroupCtr]; + } + } + else + { + PrintError("ERROR: GetActiveProcessorCount address not obtained with error: %d.!\r\n", GetLastError()); + return false; + } + } + else + { + g_pActiveGroupsAndProcs->wActiveGroupCount = 1; + g_pActiveGroupsAndProcs->dwaActiveProcsCount[0] = 0; + + GetSystemInfo(&SystemInfo); + g_pActiveGroupsAndProcs->dwaActiveProcsCount[0] = SystemInfo.dwNumberOfProcessors; + g_ulProcCount = g_pActiveGroupsAndProcs->dwaActiveProcsCount[0]; + + if (g_ulProcCount < 1) + { + PrintError("ERROR: Processor count = %d\n", g_pActiveGroupsAndProcs->dwaActiveProcsCount[0]); + PrintError("ERROR: Incorrect value; there has to be at least 1 processor in the system\n"); + return false; + } + } + if (g_pActiveGroupsAndProcs->wActiveGroupCount > 1 || g_ulProcCount > 64) + { + PrintError("WARNING: Complete CPU utilization cannot currently be gathered within DISKSPD for this system.\n" + " Use alternate mechanisms to gather this data such as perfmon/logman.\n" + " Active KGroups %u > 1 and/or processor count %u > 64.\n", + g_pActiveGroupsAndProcs->wActiveGroupCount, + g_ulProcCount); + } + return true; +} + +VOID SetProcGroupMask(WORD wGroupNum, DWORD dwProcNum, GROUP_AFFINITY *pGroupAffinity) +{ + //must zero this structure first, otherwise it fails to set affinity + memset(pGroupAffinity, 0, sizeof(GROUP_AFFINITY)); + + pGroupAffinity->Group = (USHORT)wGroupNum; + pGroupAffinity->Mask = (KAFFINITY)1<Mask)); + if (dwpPrevMask == 0) + { + PrintError("ERROR: SetThreadAffinityMask failed with error: %d.!\r\n", GetLastError()); + return FALSE; + } + + return TRUE; + } +} + +/*****************************************************************************/ +void IORequestGenerator::_CloseOpenFiles(vector& vhFiles) const +{ + for (size_t x = 0; x < vhFiles.size(); ++x) + { + if ((INVALID_HANDLE_VALUE != vhFiles[x]) && (nullptr != vhFiles[x])) + { + if (!CloseHandle(vhFiles[x])) + { + PrintError("Warning: unable to close file handle (error code: %u)\n", GetLastError()); + } + vhFiles[x] = nullptr; + } + } +} + +/*****************************************************************************/ +// wrapper for pfnPrintOut. printf cannot be used directly, because IORequestGenerator.dll +// may be consumed by gui app which doesn't have stdout +static void print(const char *format, ...) +{ + assert(NULL != format); + + if( NULL != g_pfnPrintOut ) + { + va_list listArg; + va_start(listArg, format); + g_pfnPrintOut(format, listArg); + va_end(listArg); + } +} + +/*****************************************************************************/ +// wrapper for pfnPrintError. fprintf(stderr) cannot be used directly, because IORequestGenerator.dll +// may be consumed by gui app which doesn't have stdout +void PrintError(const char *format, ...) +{ + assert(NULL != format); + + if( NULL != g_pfnPrintError ) + { + va_list listArg; + + va_start(listArg, format); + g_pfnPrintError(format, listArg); + va_end(listArg); + } +} + +/*****************************************************************************/ +// prints the string only if verbose mode is set to true +// +static void printfv(bool fVerbose, const char *format, ...) +{ + assert(NULL != format); + + if( NULL != g_pfnPrintVerbose && fVerbose ) + { + va_list argList; + va_start(argList, format); + g_pfnPrintVerbose(format, argList); + va_end(argList); + } +} + +/*****************************************************************************/ +// thread for gathering ETW data (etw functions are defined in etw.cpp) +// +DWORD WINAPI etwThreadFunc(LPVOID cookie) +{ + UNREFERENCED_PARAMETER(cookie); + + g_bTracing = TRUE; + BOOL result = TraceEvents(); + g_bTracing = FALSE; + + return result ? 0 : 1; +} + +/*****************************************************************************/ +// display file size in a user-friendly form using 'verbose' stream +// +void IORequestGenerator::_DisplayFileSizeVerbose(bool fVerbose, UINT64 fsize) const +{ + if( fsize > (UINT64)10*1024*1024*1024 ) // > 10GB + { + printfv(fVerbose, "%I64uGB", fsize >> 30); + } + else if( fsize > (UINT64)10*1024*1024 ) // > 10MB + { + printfv(fVerbose, "%I64uMB", fsize >> 20); + } + else if( fsize > 10*1024 ) // > 10KB + { + printfv(fVerbose, "%I64uKB", fsize >> 10); + } + else + { + printfv(fVerbose, "%I64uB", fsize); + } +} + +/*****************************************************************************/ +// generate 64-bit random number +static ULONG64 rand64() +{ + return + ((((ULONG64)rand()) & 0x7fff) | + ((((ULONG64)rand()) & 0x7fff) << 15) | + ((((ULONG64)rand()) & 0x7fff) << 30) | + (((((ULONG64)rand()) & 0x7fff) << 30) << 15) | + (((((ULONG64)rand()) & 0xF) << 30) << 30)); +} + +/*****************************************************************************/ +bool IORequestGenerator::_LoadDLLs() +{ + _hNTDLL = LoadLibraryExW(L"ntdll.dll", nullptr, 0); + if( nullptr == _hNTDLL ) + { + return false; + } + + g_pfnNtQuerySysInfo = (NtQuerySysInfo)GetProcAddress(_hNTDLL, "NtQuerySystemInformation"); + if( nullptr == g_pfnNtQuerySysInfo ) + { + return false; + } + + return true; +} + +/*****************************************************************************/ +// returns the number of CPUs present in the system +// +static ULONG getProcessorCount() +{ + SYSTEM_INFO SystemInfo; + + // get system information + GetSystemInfo(&SystemInfo); + + // and extract number or processors from there + return (ULONG)SystemInfo.dwNumberOfProcessors; +} + +/*****************************************************************************/ +// returns affinity mask +// +static DWORD_PTR getCPUMask(ULONG ulProcNum) +{ + assert(ulProcNum < 8 * sizeof(DWORD_PTR)); + + return ((DWORD_PTR)1) << ulProcNum; +} + +/*****************************************************************************/ +bool IORequestGenerator::_GetSystemPerfInfo(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION *pInfo, UINT32 uCpuCount) const +{ + NTSTATUS Status = NO_ERROR; + + assert(NULL != pInfo); + assert(uCpuCount > 0); + + Status = g_pfnNtQuerySysInfo(SystemProcessorPerformanceInformation, + pInfo, + sizeof(*pInfo) * uCpuCount, + NULL); + + return NT_SUCCESS(Status); +} + +/*****************************************************************************/ +// calculate the offset of the next I/O operation +// + +__inline UINT64 IORequestGenerator::GetNextFileOffset(ThreadParameters& tp, size_t targetNum, UINT64 prevOffset) +{ + Target &target = tp.vTargets[targetNum]; + + UINT64 blockAlignment = target.GetBlockAlignmentInBytes(); + UINT64 baseFileOffset = target.GetBaseFileOffsetInBytes(); + UINT64 blockSize = target.GetBlockSizeInBytes(); + UINT64 nextBlockOffset; + + // increment/produce - note, logically relative to base offset + if (target.GetUseRandomAccessPattern()) + { + nextBlockOffset = rand64(); + nextBlockOffset -= (nextBlockOffset % blockAlignment); + } + else if (target.GetUseParallelAsyncIO()) + { + nextBlockOffset = prevOffset - baseFileOffset + blockAlignment; + } + else if (target.GetUseInterlockedSequential()) + { + nextBlockOffset = InterlockedAdd64((PLONGLONG) &tp.pullSharedSequentialOffsets[targetNum], blockAlignment) - blockAlignment; + } + else // normal sequential access pattern + { + nextBlockOffset = (tp.vullPrivateSequentialOffsets[targetNum] += blockAlignment); + } + + // now apply bounds for IO offset + // aligned target size is the closed interval of byte offsets at which it is legal to issue IO + // ISSUE IMPROVEMENT: much of this should be precalculated. It belongs within Target, which will + // need discovery of target sizing moved from its current just-in-time at thread launch. + UINT64 alignedTargetSize = tp.vullFileSizes[targetNum] - baseFileOffset - blockSize; + if (target.GetUseRandomAccessPattern() || + target.GetUseInterlockedSequential()) + { + // these access patterns occur on blockaligned boundaries relative to base + // convert aligned target size to the open interval + alignedTargetSize = ((alignedTargetSize / blockAlignment) + 1) * blockAlignment; + nextBlockOffset %= alignedTargetSize; + } + else + { + // parasync and seq bases are potentially modified by threadstride and loop back to the + // file base offset + increment which will return them to their initial base offset. + if (nextBlockOffset > alignedTargetSize) + { + nextBlockOffset = (IORequestGenerator::GetThreadBaseFileOffset(tp, targetNum) - baseFileOffset) % blockAlignment; + tp.vullPrivateSequentialOffsets[targetNum] = nextBlockOffset; + } + } + + // Convert into the next full offset + nextBlockOffset += baseFileOffset; + +#ifndef NDEBUG + // Don't overrun the end of the file + UINT64 fileSize = tp.vullFileSizes[targetNum]; + assert(nextBlockOffset + blockSize <= fileSize); +#endif + + return nextBlockOffset; +} + +__inline UINT64 IORequestGenerator::GetThreadBaseFileOffset(ThreadParameters& tp, size_t targetNum) +{ + const Target &target = tp.vTargets[targetNum]; + + UINT64 baseFileOffset = target.GetBaseFileOffsetInBytes(); + UINT64 nextBlockOffset; + + if (target.GetUseRandomAccessPattern()) + { + nextBlockOffset = IORequestGenerator::GetNextFileOffset(tp, targetNum, 0); + } + else + { + // interlocked sequential - thread stride is always zero, enforced during profile validation + // parallel async - apply thread stride + // sequential - apply thread stride + nextBlockOffset = baseFileOffset + tp.ulRelativeThreadNo * target.GetThreadStrideInBytes(); + } + + return nextBlockOffset; +} + +__inline UINT64 IORequestGenerator::GetStartingFileOffset(ThreadParameters& tp, size_t targetNum) +{ + const Target &target = tp.vTargets[targetNum]; + + UINT64 baseFileOffset = target.GetBaseFileOffsetInBytes(); + UINT64 nextBlockOffset; + + if (target.GetUseRandomAccessPattern()) + { + nextBlockOffset = IORequestGenerator::GetNextFileOffset(tp, targetNum, 0); + } + else + { + // interlocked sequential - getnext starts the clock from zero, thread independent + // parallel async - getthreadbase, thread dependent + // sequential - "", and initialize private counter + if (target.GetUseInterlockedSequential()) + { + nextBlockOffset = IORequestGenerator::GetNextFileOffset(tp, targetNum, 0); + } + else + { + nextBlockOffset = IORequestGenerator::GetThreadBaseFileOffset(tp, targetNum); + + if (!target.GetUseParallelAsyncIO()) + { + tp.vullPrivateSequentialOffsets[targetNum] = nextBlockOffset - baseFileOffset; + } + } + } + + return nextBlockOffset; +} + +/*****************************************************************************/ +// Decide the kind of IO to issue during a mix test +// Future Work: Add more types of distribution in addition to random +__inline static IOOperation DecideIo(UINT32 ulWriteRatio) +{ + return (((UINT32)abs(rand() % 100 + 1)) > ulWriteRatio) ? IOOperation::ReadIO : IOOperation::WriteIO; + } + +/*****************************************************************************/ +// function called from worker thread +// performs asynch I/O using IO Completion Ports +// +__inline static bool doWorkUsingIOCompletionPorts(ThreadParameters *p, HANDLE hCompletionPort) +{ + assert(nullptr!= p); + assert(nullptr != hCompletionPort); + + bool fOk = true; + + LARGE_INTEGER li; + BOOL rslt = FALSE; + OVERLAPPED * pCompletedOvrp; + ULONG_PTR ulCompletionKey; + DWORD dwBytesTransferred; + DWORD dwIOCnt = 0; + OverlappedQueue overlappedQueue; + size_t cOverlapped = p->vOverlapped.size(); + + bool fMeasureLatency = p->pTimeSpan->GetMeasureLatency(); + + size_t cTargets = p->vTargets.size(); + vector vThroughputMeters(cTargets); + bool fUseThrougputMeter = false; + // TODO: move to a separate function + for (size_t i = 0; i < cTargets; i++) + { + Target *pTarget = &p->vTargets[i]; + DWORD dwBurstSize = pTarget->GetBurstSize(); + if (p->pTimeSpan->GetThreadCount() > 0) + { + dwBurstSize /= p->pTimeSpan->GetThreadCount(); + } + else + { + dwBurstSize /= pTarget->GetThreadsPerFile(); + } + + if (pTarget->GetThroughputInBytesPerMillisecond() > 0 || pTarget->GetThinkTime() > 0) + { + fUseThrougputMeter = true; + vThroughputMeters[i].Start(pTarget->GetThroughputInBytesPerMillisecond(), pTarget->GetBlockSizeInBytes(), pTarget->GetThinkTime(), dwBurstSize); + } + } + + //start IO operations + for (size_t i = 0; i < cOverlapped; i++) + { + overlappedQueue.Add(&p->vOverlapped[i]); + } + + // + // perform work + // + while(g_bRun && !g_bThreadError) + { + DWORD dwMinSleepTime = ~((DWORD)0); + for (size_t i = 0; i < overlappedQueue.GetCount(); i++) + { + OVERLAPPED *pReadyOverlapped = overlappedQueue.Remove(); + DWORD iOverlapped = (DWORD)(pReadyOverlapped - &p->vOverlapped[0]); + size_t iTarget = p->vOverlappedIdToTargetId[iOverlapped]; + size_t iRequest = iOverlapped - p->vFirstOverlappedIdForTargetId[iTarget]; + Target *pTarget = &p->vTargets[iTarget]; + ThroughputMeter *pThroughputMeter = &vThroughputMeters[iTarget]; + + DWORD dwSleepTime = pThroughputMeter->GetSleepTime(); + if (pThroughputMeter->IsRunning() && dwSleepTime > 0) + { + dwMinSleepTime = min(dwMinSleepTime, dwSleepTime); + overlappedQueue.Add(pReadyOverlapped); + continue; + } + + if (fMeasureLatency) + { + p->vIoStartTimes[iOverlapped] = PerfTimer::GetTime(); // record IO start time + } + + IOOperation readOrWrite; + readOrWrite = p->vdwIoType[iOverlapped] = DecideIo(pTarget->GetWriteRatio()); + if (readOrWrite == IOOperation::ReadIO) + { + rslt = ReadFile(p->vhTargets[iTarget], p->GetReadBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes(), nullptr, pReadyOverlapped); + } + else + { + rslt = WriteFile(p->vhTargets[iTarget], p->GetWriteBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes(), nullptr, pReadyOverlapped); + } + + if (!rslt && GetLastError() != ERROR_IO_PENDING) + { + PrintError("t[%u] error during %s error code: %u)\n", iOverlapped, (readOrWrite == IOOperation::ReadIO ? "read" : "write"), GetLastError()); + fOk = false; + goto cleanup; + } + + if (pThroughputMeter->IsRunning()) + { + pThroughputMeter->Adjust(pTarget->GetBlockSizeInBytes()); + } + } + + // if no IOs are in flight, wait for the next scheduling time + if (fUseThrougputMeter && (overlappedQueue.GetCount() == p->vOverlapped.size()) && dwMinSleepTime != ~((DWORD)0)) + { + Sleep(dwMinSleepTime); + } + + // wait till one of the IO operations finishes + if (GetQueuedCompletionStatus(hCompletionPort, &dwBytesTransferred, &ulCompletionKey, &pCompletedOvrp, 1) != 0) + { + //find which I/O operation it was (so we know to which buffer should we use) + DWORD iOverlapped = (DWORD)(pCompletedOvrp - &p->vOverlapped[0]); + size_t iTarget = p->vOverlappedIdToTargetId[iOverlapped]; + + //check if I/O transferred all of the requested bytes + Target *pTarget = &p->vTargets[iTarget]; + if (dwBytesTransferred != pTarget->GetBlockSizeInBytes()) + { + PrintError("Warning: thread %u transferred %u bytes instead of %u bytes\n", + p->ulThreadNo, + dwBytesTransferred, + pTarget->GetBlockSizeInBytes()); + } + + li.HighPart = pCompletedOvrp->OffsetHigh; + li.LowPart = pCompletedOvrp->Offset; + + if (*p->pfAccountingOn) + { + p->pResults->vTargetResults[iTarget].Add(dwBytesTransferred, + p->vdwIoType[iOverlapped], + &p->vIoStartTimes[iOverlapped], + p->pullStartTime, + fMeasureLatency, + p->pTimeSpan->GetCalculateIopsStdDev()); + } + + // TODO: move to a separate function + // check if we should print a progress dot + if (p->pProfile->GetProgress() != 0) + { + ++dwIOCnt; + if (dwIOCnt == p->pProfile->GetProgress()) + { + print("."); + dwIOCnt = 0; + } + } + + //restart the I/O operation that just completed + li.QuadPart = IORequestGenerator::GetNextFileOffset(*p, iTarget, li.QuadPart); + + pCompletedOvrp->Offset = li.LowPart; + pCompletedOvrp->OffsetHigh = li.HighPart; + + printfv(p->pProfile->GetVerbose(), "t[%u:%u] new I/O op at %I64u (starting in block: %I64u)\n", + p->ulThreadNo, + iTarget, + li.QuadPart, + li.QuadPart / pTarget->GetBlockSizeInBytes()); + + overlappedQueue.Add(pCompletedOvrp); + } + else + { + DWORD err = GetLastError(); + if (err != WAIT_TIMEOUT) + { + PrintError("error during overlapped IO operation (error code: %u)\n", err); + fOk = false; + goto cleanup; + } + } + } // end work loop + +cleanup: + return fOk; +} + +/*****************************************************************************/ +// I/O completion routine. used by ReadFileEx and WriteFileEx +// + +VOID CALLBACK fileIOCompletionRoutine(DWORD dwErrorCode, DWORD dwBytesTransferred, LPOVERLAPPED pOverlapped) +{ + assert(NULL != pOverlapped); + + BOOL rslt = FALSE; + LARGE_INTEGER li; + + ThreadParameters *p = (ThreadParameters *)pOverlapped->hEvent; + bool fMeasureLatency = p->pTimeSpan->GetMeasureLatency(); + + assert(NULL != p); + + //check error code + if (0 != dwErrorCode) + { + PrintError("Thread %u failed executing an I/O operation (error code: %u)\n", p->ulThreadNo, dwErrorCode); + goto cleanup; + } + + size_t iOverlapped = (pOverlapped - &p->vOverlapped[0]); + size_t iTarget = p->vOverlappedIdToTargetId[iOverlapped]; + Target *pTarget = &p->vTargets[iTarget]; + + //check if I/O operation transferred requested number of bytes + if (dwBytesTransferred != pTarget->GetBlockSizeInBytes()) + { + PrintError("Warning: thread %u transferred %u bytes instead of %u bytes\n", + p->ulThreadNo, + dwBytesTransferred, + pTarget->GetBlockSizeInBytes()); + } + + // check if we should print a progress dot + // BUGBUG: does not work ... io counter must be global + DWORD cdwIO = 0; + if (p->pProfile->GetProgress() != 0) + { + ++cdwIO; + if (cdwIO == p->pProfile->GetProgress()) + { + print("."); + cdwIO = 0; + } + } + + if (*p->pfAccountingOn) + { + p->pResults->vTargetResults[iTarget].Add(dwBytesTransferred, + p->vdwIoType[iOverlapped], + &p->vIoStartTimes[iOverlapped], + p->pullStartTime, + fMeasureLatency, + p->pTimeSpan->GetCalculateIopsStdDev()); + } + + //restart the I/O operation that just completed + li.HighPart = pOverlapped->OffsetHigh; + li.LowPart = pOverlapped->Offset; + + li.QuadPart = IORequestGenerator::GetNextFileOffset(*p, iTarget, li.QuadPart); + + pOverlapped->Offset = li.LowPart; + pOverlapped->OffsetHigh = li.HighPart; + + printfv(p->pProfile->GetVerbose(), "t[%u:%u] new I/O op at %I64u (starting in block: %I64u)\n", + p->ulThreadNo, + iTarget, + li.QuadPart, + li.QuadPart / pTarget->GetBlockSizeInBytes()); + + // start a new IO operation + if (g_bRun && !g_bThreadError) + { + size_t iRequest = iOverlapped - p->vFirstOverlappedIdForTargetId[iTarget]; + if (fMeasureLatency) + { + p->vIoStartTimes[iOverlapped] = PerfTimer::GetTime(); // record IO start time + } + + IOOperation readOrWrite; + readOrWrite = p->vdwIoType[iOverlapped] = DecideIo(pTarget->GetWriteRatio()); + if (readOrWrite == IOOperation::ReadIO) + { + rslt = ReadFileEx(p->vhTargets[iTarget], p->GetReadBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes(), pOverlapped, fileIOCompletionRoutine); + } + else + { + rslt = WriteFileEx(p->vhTargets[iTarget], p->GetWriteBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes(), pOverlapped, fileIOCompletionRoutine); + } + + if (!rslt) + { + PrintError("t[%u:%u] error during %s error code: %u)\n", p->ulThreadNo, iTarget, (readOrWrite == IOOperation::ReadIO ? "read" : "write"), GetLastError()); + goto cleanup; + } + } + +cleanup: + return; +} + +/*****************************************************************************/ +// function called from worker thread +// performs asynch I/O using IO Completion Routines (ReadFileEx, WriteFileEx) +// +__inline static bool doWorkUsingCompletionRoutines(ThreadParameters *p) +{ + assert(NULL != p); + bool fOk = true; + BOOL rslt = FALSE; + + //start IO operations + size_t iOverlapped = 0; + + bool fMeasureLatency = p->pTimeSpan->GetMeasureLatency(); + + for (size_t iTarget = 0; iTarget < p->vTargets.size(); iTarget++) + { + Target *pTarget = &p->vTargets[iTarget]; + for (size_t iRequest = 0; iRequest < pTarget->GetRequestCount(); ++iRequest) + { + if (fMeasureLatency) + { + p->vIoStartTimes[iOverlapped] = PerfTimer::GetTime(); // record IO start time + } + + IOOperation readOrWrite; + readOrWrite = p->vdwIoType[iOverlapped] = DecideIo(pTarget->GetWriteRatio()); + if (readOrWrite == IOOperation::ReadIO) + { + rslt = ReadFileEx(p->vhTargets[iTarget], p->GetReadBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes(), &p->vOverlapped[iOverlapped], fileIOCompletionRoutine); + } + else + { + rslt = WriteFileEx(p->vhTargets[iTarget], p->GetWriteBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes(), &p->vOverlapped[iOverlapped], fileIOCompletionRoutine); + } + + if (!rslt) + { + PrintError("t[%u:%u] error during %s error code: %u)\n", p->ulThreadNo, iTarget, (readOrWrite == IOOperation::ReadIO ? "read" : "write"), GetLastError()); + fOk = false; + goto cleanup; + } + iOverlapped++; + } + } + + DWORD dwWaitResult = 0; + while( g_bRun && !g_bThreadError ) + { + dwWaitResult = WaitForSingleObjectEx(p->hEndEvent, INFINITE, TRUE); + + assert(WAIT_IO_COMPLETION == dwWaitResult || (WAIT_OBJECT_0 == dwWaitResult && (!g_bRun || g_bThreadError))); + + //check WaitForSingleObjectEx status + if( WAIT_IO_COMPLETION != dwWaitResult && WAIT_OBJECT_0 != dwWaitResult ) + { + PrintError("Error in thread %u during WaitForSingleObjectEx (in completion routines)\n", p->ulThreadNo); + fOk = false; + goto cleanup; + } + } +cleanup: + return fOk; +} + +/*****************************************************************************/ +// worker thread function +// +DWORD WINAPI threadFunc(LPVOID cookie) +{ + /// for XP/20003 support + static bool once = true; + NT6_SET_FILE_INFORMATION_BY_HANDLE Nt6SetFileInformationByHandle = NULL; + + if (once) + { + //load kernel32.dll + HMODULE kernel32; + kernel32 = LoadLibraryExW(L"kernel32.dll", NULL, 0); + if (kernel32 == NULL) + { + PrintError("ERROR: kernel32.dll library failed to load!\r\n"); + return FALSE; + } + + //get function address from kernel32.dll + Nt6SetFileInformationByHandle = (NT6_SET_FILE_INFORMATION_BY_HANDLE) GetProcAddress( + kernel32, SET_FILE_INFORMATION_BY_HANDLE); + + once = false; + } + /// + + + bool fOk = true; + ThreadParameters *p = reinterpret_cast(cookie); + HANDLE hCompletionPort = nullptr; + + bool fMeasureLatency = p->pTimeSpan->GetMeasureLatency(); + bool fCalculateIopsStdDev = p->pTimeSpan->GetCalculateIopsStdDev(); + UINT64 ioBucketDuration = 0; + UINT32 expectedNumberOfBuckets = 0; + if(fCalculateIopsStdDev) + { + UINT32 ioBucketDurationInMilliseconds = p->pTimeSpan->GetIoBucketDurationInMilliseconds(); + ioBucketDuration = PerfTimer::MillisecondsToPerfTime(ioBucketDurationInMilliseconds); + expectedNumberOfBuckets = Util::QuotientCeiling(p->pTimeSpan->GetDuration() * 1000, ioBucketDurationInMilliseconds); + } + + //set random seed (each thread has a different one) + srand(p->ulRandSeed); + + //affinity + ULONG ulGroupProcs = 0; + if (!p->pTimeSpan->GetGroupAffinity()) + { + assert(g_ulProcCount > 0); + ulGroupProcs = getProcessorCount(); + } + + //simple affinity + if (!p->pTimeSpan->GetDisableAffinity() && (p->pTimeSpan->GetAffinityAssignments().size() == 0) && !p->pTimeSpan->GetGroupAffinity()) + { + HANDLE hThread = GetCurrentThread(); + ULONG ulProcNum = p->ulThreadNo % ulGroupProcs; + printfv(p->pProfile->GetVerbose(), "affinitizing thread %u to CPU%u\n", p->ulThreadNo, ulProcNum); + + // set thread affinity + if (0 == SetThreadAffinityMask(hThread, getCPUMask(ulProcNum))) + { + PrintError("Error setting affinity mask in thread %u\n", p->ulThreadNo); + fOk = false; + goto cleanup; + } + + // set thread ideal processor + if ((DWORD)-1 == SetThreadIdealProcessor(hThread, ulProcNum)) + { + PrintError("Error setting ideal processor in thread %u\n", p->ulThreadNo); + fOk = false; + goto cleanup; + } + } + + //advanced affinity + if (!p->pTimeSpan->GetDisableAffinity() && (p->pTimeSpan->GetAffinityAssignments().size() > 0) && !p->pTimeSpan->GetGroupAffinity()) + { + vector vAffinity(p->pTimeSpan->GetAffinityAssignments()); + + ULONG ulProcNum = p->ulThreadNo % vAffinity.size(); + UINT32 proc = vAffinity[ulProcNum]; + + assert(proc < g_ulProcCount); + if (ulProcNum >= g_ulProcCount) + { + PrintError("Invalid affinity mask (CPU id cannot be larger than the number of CPUs in the system)\n"); + fOk = false; + goto cleanup; + } + + printfv(p->pProfile->GetVerbose(), "affinitizing thread %u to CPU%u\n", p->ulThreadNo, proc); + + HANDLE hThread = GetCurrentThread(); + + // set thread affinity + if (0 == SetThreadAffinityMask(hThread, getCPUMask(proc))) + { + PrintError("Error setting affinity mask in thread %u\n", p->ulThreadNo); + fOk = false; + goto cleanup; + } + + // set thread ideal processor + if ((DWORD)-1 == SetThreadIdealProcessor(hThread, proc)) + { + PrintError("Error setting ideal processor in thread %u\n", p->ulThreadNo); + fOk = false; + goto cleanup; + } + } + + //group affinity + if (!p->pTimeSpan->GetDisableAffinity() && (p->pTimeSpan->GetAffinityAssignments().size() == 0) && p->pTimeSpan->GetGroupAffinity()) + { + SetProcGroupMask(p->wGroupNum, p->dwProcNum, &p->GroupAffinity); + + HANDLE hThread = GetCurrentThread(); + if (SetThreadGroupAndProcAffinity(hThread, &p->GroupAffinity, nullptr) == FALSE) + { + PrintError("Error setting affinity mask in thread %u\n", p->ulThreadNo); + fOk = false; + goto cleanup; + } + } + + // adjust thread token if large pages are needed + for (auto pTarget = p->vTargets.begin(); pTarget != p->vTargets.end(); pTarget++) + { + if (pTarget->GetUseLargePages()) + { + if (!SetPrivilege(SE_LOCK_MEMORY_NAME)) + { + fOk = false; + goto cleanup; + } + break; + } + } + + // TODO: open files + size_t iTarget = 0; + for (auto pTarget = p->vTargets.begin(); pTarget != p->vTargets.end(); pTarget++) + { + bool fPhysical = false; + bool fPartition = false; + + string sPath(pTarget->GetPath()); + const char *filename = sPath.c_str(); + + const char *fname = nullptr; //filename (can point to physFN) + char physFN[32]; //disk/partition name + + if (NULL == filename || NULL == *(filename)) + { + PrintError("FATAL ERROR: invalid filename\n"); + fOk = false; + goto cleanup; + } + + //check if it is a physical drive + if ('#' == *filename && NULL != *(filename + 1)) + { + UINT32 nDriveNo = (UINT32)atoi(filename + 1); + fPhysical = true; + sprintf_s(physFN, 32, "\\\\.\\PhysicalDrive%u", nDriveNo); + fname = physFN; + } + + //check if it is a partition + if (!fPhysical && NULL != *(filename + 1) && NULL == *(filename + 2) && isalpha((unsigned char)filename[0]) && ':' == filename[1]) + { + fPartition = true; + + sprintf_s(physFN, 32, "\\\\.\\%c:", filename[0]); + fname = physFN; + } + + //check if it is a regular file + if (!fPhysical && !fPartition) + { + fname = sPath.c_str(); + } + + //set file flags + DWORD dwFlags = FILE_ATTRIBUTE_NORMAL; + + if (pTarget->GetSequentialScanHint()) + { + dwFlags |= FILE_FLAG_SEQUENTIAL_SCAN; + } + + if (pTarget->GetRandomAccessHint()) + { + dwFlags |= FILE_FLAG_RANDOM_ACCESS; + } + + if ((pTarget->GetRequestCount() > 1) || (p->vTargets.size() > 1)) + { + dwFlags |= FILE_FLAG_OVERLAPPED; + } + + if (pTarget->GetDisableOSCache()) + { + dwFlags |= FILE_FLAG_NO_BUFFERING; + } + + if (pTarget->GetDisableAllCache()) + { + dwFlags |= (FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH); + } + + DWORD dwDesiredAccess = 0; + if (pTarget->GetWriteRatio() == 0) + { + dwDesiredAccess = GENERIC_READ; + } + else if (pTarget->GetWriteRatio() == 100) + { + dwDesiredAccess = GENERIC_WRITE; + } + else + { + dwDesiredAccess = GENERIC_READ | GENERIC_WRITE; + } + + HANDLE hFile = CreateFile(fname, + dwDesiredAccess, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, //security + OPEN_EXISTING, + dwFlags, //flags (add overlapping) + nullptr); //template file + if (INVALID_HANDLE_VALUE == hFile) + { + // TODO: error out + PrintError("Error opening file: %s [%u]\n", sPath.c_str(), GetLastError()); + fOk = false; + goto cleanup; + } + + p->vhTargets.push_back(hFile); + + //set IO priority + if (Nt6SetFileInformationByHandle && pTarget->GetIOPriorityHint() != IoPriorityHintNormal) + { + FILE_IO_PRIORITY_HINT_INFO hintInfo; + hintInfo.PriorityHint = pTarget->GetIOPriorityHint(); + if (!Nt6SetFileInformationByHandle(hFile, FileIoPriorityHintInfo, &hintInfo, sizeof(hintInfo))) + { + PrintError("Error setting IO priority for file: %s [%u]\n", sPath.c_str(), GetLastError()); + fOk = false; + goto cleanup; + } + } + + // obtain file/disk/partition size + { + UINT64 fsize = 0; //file size + + //check if it is a disk + if (fPhysical) + { + fsize = GetPhysicalDriveSize(hFile); + } + // check if it is a partition + else if (fPartition) + { + fsize = GetPartitionSize(hFile); + } + // it has to be a regular file + else + { + ULARGE_INTEGER ulsize; + + ulsize.LowPart = GetFileSize(hFile, &ulsize.HighPart); + if (INVALID_FILE_SIZE == ulsize.LowPart && GetLastError() != NO_ERROR) + { + PrintError("Error getting file size\n"); + fOk = false; + goto cleanup; + } + else + { + fsize = ulsize.QuadPart; + } + } + + // check if file size is valid (if it's == 0, it won't be useful) + if (0 == fsize) + { + // TODO: error out + PrintError("The file is too small or there has been an error during getting file size\n"); + fOk = false; + goto cleanup; + } + + if (fsize < pTarget->GetMaxFileSize()) + { + PrintError("Warning - file size is less than MaxFileSize\n"); + } + + if (pTarget->GetMaxFileSize() > 0) + { + // user wants to use only a part of the target + // if smaller, of course use the entire content + p->vullFileSizes.push_back(pTarget->GetMaxFileSize() > fsize ? fsize : pTarget->GetMaxFileSize()); + } + else + { + // the whole file will be used + p->vullFileSizes.push_back(fsize); + } + + UINT64 startingFileOffset = IORequestGenerator::GetThreadBaseFileOffset(*p, iTarget); + + // test whether the file is large enough for this thread to do work + if (startingFileOffset + pTarget->GetBlockSizeInBytes() >= p->vullFileSizes[iTarget]) + { + PrintError("The file is too small. File: '%s' relative thread %u size: %I64u, base offset: %I64u block size: %u\n", + pTarget->GetPath().c_str(), + p->ulRelativeThreadNo, + fsize, + pTarget->GetBaseFileOffsetInBytes(), + pTarget->GetBlockSizeInBytes()); + fOk = false; + goto cleanup; + } + + if (pTarget->GetUseRandomAccessPattern()) + { + printfv(p->pProfile->GetVerbose(), "thread %u starting: file '%s' relative thread %u random pattern\n", + p->ulThreadNo, + pTarget->GetPath().c_str(), + p->ulRelativeThreadNo); + } + else + { + printfv(p->pProfile->GetVerbose(), "thread %u starting: file '%s' relative thread %u file offset: %I64u (starting in block: %I64u)\n", + p->ulThreadNo, + pTarget->GetPath().c_str(), + p->ulRelativeThreadNo, + startingFileOffset, + startingFileOffset / pTarget->GetBlockSizeInBytes()); + } + } + + // allocate memory for a data buffer + if (!p->AllocateAndFillBufferForTarget(*pTarget)) + { + PrintError("FATAL ERROR: Could not allocate a buffer bytes for target '%s'. Error code: 0x%x\n", pTarget->GetPath().c_str(), GetLastError()); + fOk = false; + goto cleanup; + } + iTarget++; + } + + // TODO: copy parameters for better memory locality? + // TODO: tell the main thread we're ready + // TODO: wait for a signal to start + + printfv(p->pProfile->GetVerbose(), "thread %u started (random seed: %u)\n", p->ulThreadNo, p->ulRandSeed); + + // TODO: check if it's still used + LARGE_INTEGER li; //used for setting file positions, etc. + DWORD dwIOCnt = 0; //number of completed I/O operations since last progress dot + + p->vullPrivateSequentialOffsets.clear(); + p->vullPrivateSequentialOffsets.resize(p->vTargets.size()); + p->pResults->vTargetResults.clear(); + p->pResults->vTargetResults.resize(p->vTargets.size()); + for (size_t i = 0; i < p->vullFileSizes.size(); i++) + { + p->pResults->vTargetResults[i].sPath = p->vTargets[i].GetPath(); + p->pResults->vTargetResults[i].ullFileSize = p->vullFileSizes[i]; + if(fCalculateIopsStdDev) + { + p->pResults->vTargetResults[i].readBucketizer.Initialize(ioBucketDuration, expectedNumberOfBuckets); + p->pResults->vTargetResults[i].writeBucketizer.Initialize(ioBucketDuration, expectedNumberOfBuckets); + } + } + + // + // synchronous access + // + //FUTURE EXTENSION: enable asynchronous I/O even if only 1 outstanding I/O per file (requires another parameter) + + if (p->vTargets.size() == 1 && p->vTargets[0].GetRequestCount() == 1) + { + Target *pTarget = &p->vTargets[0]; + DWORD dwBytesTransferred = 0; + + //advance file pointer to base file offset + li.QuadPart = IORequestGenerator::GetStartingFileOffset(*p, 0); + printfv(p->pProfile->GetVerbose(), "t[%u] initial I/O op at %I64u (starting in block: %I64u)\n", + p->ulThreadNo, + li.QuadPart, + li.QuadPart / pTarget->GetBlockSizeInBytes()); + //FUTURE EXTENSION: file pointer should be set through OVERLAPPED stucture for consistency with other scenarios (unless this is suspected to be the common way in real scenarios) + if (!SetFilePointerEx(p->vhTargets[0], li, NULL, FILE_BEGIN)) + { + PrintError("Error setting file pointer. Error code: %d.\n", GetLastError()); + fOk = false; + goto cleanup; + } + + BOOL rslt = FALSE; + + assert(nullptr != p->hStartEvent); + + //wait for a signal to start + printfv(p->pProfile->GetVerbose(), "thread %u: waiting for a signal to start\n", p->ulThreadNo); + if (WAIT_FAILED == WaitForSingleObject(p->hStartEvent, INFINITE)) + { + PrintError("Waiting for a signal to start failed (error code: %u)\n", GetLastError()); + fOk = false; + goto cleanup; + } + printfv(p->pProfile->GetVerbose(), "thread %u: received signal to start\n", p->ulThreadNo); + + // TODO: check if this is needed + //check if everything is ok + if (g_bError) + { + fOk = false; + goto cleanup; + } + + // + // perform work + // + + assert(nullptr != p->vhTargets[0] ); + assert(pTarget->GetBlockSizeInBytes() > 0); + + ThroughputMeter throughputMeter; + DWORD dwSleepTime; + + DWORD dwBurstSize = pTarget->GetBurstSize(); + if (p->pTimeSpan->GetThreadCount() > 0) + { + dwBurstSize /= p->pTimeSpan->GetThreadCount(); + } + else + { + dwBurstSize /= pTarget->GetThreadsPerFile(); + } + throughputMeter.Start(pTarget->GetThroughputInBytesPerMillisecond(), pTarget->GetBlockSizeInBytes(), pTarget->GetThinkTime(), dwBurstSize); + + while(g_bRun && !g_bThreadError) + { + if (throughputMeter.IsRunning()) + { + dwSleepTime = throughputMeter.GetSleepTime(); + if (0 != dwSleepTime) + { + Sleep(dwSleepTime); + continue; + } + } + + //start read or write operation (depends of the type of test) + //first access is always performed on base offset (even in case of random access) + + UINT64 ullStartTime = 0; + + if (fMeasureLatency) + { + ullStartTime = PerfTimer::GetTime(); // record IO start time + } + + IOOperation readOrWrite; + readOrWrite = DecideIo(pTarget->GetWriteRatio()); + if (readOrWrite == IOOperation::ReadIO) + { + rslt = ReadFile(p->vhTargets[0], p->GetReadBuffer(0, 0), pTarget->GetBlockSizeInBytes(), &dwBytesTransferred, nullptr); + } + else + { + rslt = WriteFile(p->vhTargets[0], p->GetWriteBuffer(0, 0), pTarget->GetBlockSizeInBytes(), &dwBytesTransferred, nullptr); + } + + if (!rslt) + { + PrintError("t[%u:%u] error during %s error code: %u)\n", p->ulThreadNo, 0, (readOrWrite == IOOperation::ReadIO ? "read" : "write"), GetLastError()); + fOk = false; + goto cleanup; + } + + //check if I/O operation transferred requested number of bytes + if (dwBytesTransferred != pTarget->GetBlockSizeInBytes()) + { + PrintError("Warning: thread %u transfered %u bytes instead of %u bytes\n", p->ulThreadNo, dwBytesTransferred, pTarget->GetBlockSizeInBytes()); + } + + if (throughputMeter.IsRunning()) + { + throughputMeter.Adjust(pTarget->GetBlockSizeInBytes()); + } + + if (*p->pfAccountingOn) + { + p->pResults->vTargetResults[0].Add(dwBytesTransferred, + readOrWrite, + &ullStartTime, + p->pullStartTime, + fMeasureLatency, + fCalculateIopsStdDev); + } + + // check if we should print a progress dot + if (0 != p->pProfile->GetProgress() > 0) + { + ++dwIOCnt; + if (dwIOCnt == p->pProfile->GetProgress()) + { + print("."); + dwIOCnt = 0; + } + } + + li.QuadPart = IORequestGenerator::GetNextFileOffset(*p, 0, li.QuadPart); + + printfv(p->pProfile->GetVerbose(), "t[%u] new I/O op at %I64u (starting in block: %I64u)\n", + p->ulThreadNo, + li.QuadPart, + li.QuadPart / pTarget->GetBlockSizeInBytes()); + + if (!SetFilePointerEx(p->vhTargets[0], li, NULL, FILE_BEGIN)) + { + PrintError("thread %u: Error setting file pointer\n", p->ulThreadNo); + fOk = false; + goto cleanup; + } + + assert(!g_bError); // at this point we shouldn't be seeing initialization error + } + }//end of synchronous access + // + // overlapped IO operations + // + else + { + // + // create IO completion port + // + for (unsigned int i = 0; i < p->vTargets.size(); i++) + { + if (!p->pTimeSpan->GetCompletionRoutines()) + { + hCompletionPort = CreateIoCompletionPort(p->vhTargets[i], hCompletionPort, 0, 1); + if (nullptr == hCompletionPort) + { + PrintError("unable to create IO completion port (error code: %u)\n", GetLastError()); + fOk = false; + goto cleanup; + } + } + } + + // + // fill the OVERLAPPED structures + // + + UINT32 cOverlapped = p->GetTotalRequestCount(); + + p->vOverlapped.clear(); + p->vOverlapped.resize(cOverlapped); + + p->vdwIoType.clear(); + p->vdwIoType.resize(cOverlapped); + + p->vIoStartTimes.clear(); + p->vIoStartTimes.resize(cOverlapped); + + p->vFirstOverlappedIdForTargetId.clear(); + + UINT32 iOverlapped = 0; + for (unsigned int iFile = 0; iFile < p->vTargets.size(); iFile++) + { + Target *pTarget = &p->vTargets[iFile]; + + li.QuadPart = IORequestGenerator::GetStartingFileOffset(*p, iFile); + p->vFirstOverlappedIdForTargetId.push_back(iOverlapped); + + for (DWORD iRequest = 0; iRequest < pTarget->GetRequestCount(); ++iRequest) + { + // on increment, get next except in the case of parallel async, which all start at the initial offset. + // note that we must only do this when needed, since it will advance global state. + if (iRequest != 0 && !pTarget->GetUseParallelAsyncIO()) + { + li.QuadPart = IORequestGenerator::GetNextFileOffset(*p, iFile, li.QuadPart); + } + + p->vOverlappedIdToTargetId.push_back(iFile); + if (!p->pTimeSpan->GetCompletionRoutines()) + { + p->vOverlapped[iOverlapped].hEvent = nullptr; //we don't need event, because we use IO completion port + } + else + { + //in case of completion routines hEvent field is not used, + //so we can use it to pass a pointer to the thread parameters + p->vOverlapped[iOverlapped].hEvent = (HANDLE)p; + } + + printfv(p->pProfile->GetVerbose(), "t[%u:%u] initial I/O op at %I64u (starting in block: %I64u)\n", + p->ulThreadNo, + iFile, + li.QuadPart, + li.QuadPart / pTarget->GetBlockSizeInBytes()); + + p->vOverlapped[iOverlapped].Offset = li.LowPart; + p->vOverlapped[iOverlapped].OffsetHigh = li.HighPart; + + ++iOverlapped; + } + } + + // + // wait for a signal to start + // + printfv(p->pProfile->GetVerbose(), "thread %u: waiting for a signal to start\n", p->ulThreadNo); + if( WAIT_FAILED == WaitForSingleObject(p->hStartEvent, INFINITE) ) + { + PrintError("Waiting for a signal to start failed (error code: %u)\n", GetLastError()); + fOk = false; + goto cleanup; + } + printfv(p->pProfile->GetVerbose(), "thread %u: received signal to start\n", p->ulThreadNo); + + //check if everything is ok + if (g_bError) + { + fOk = false; + goto cleanup; + } + + //error handling and memory freeing is done in doWorkUsingIOCompletionPorts and doWorkUsingCompletionRoutines + if (!p->pTimeSpan->GetCompletionRoutines()) + { + // use IO Completion Ports (it will also close the I/O completion port) + if (!doWorkUsingIOCompletionPorts(p, hCompletionPort)) + { + fOk = false; + goto cleanup; + } + } + else + { + //use completion routines + if (!doWorkUsingCompletionRoutines(p)) + { + fOk = false; + goto cleanup; + } + } + + assert(!g_bError); // at this point we shouldn't be seeing initialization error + } // end of overlapped IO operations + + // save results + +cleanup: + if (!fOk) + { + g_bThreadError = TRUE; + } + + // free memory allocated with VirtualAlloc + for (auto i = p->vpDataBuffers.begin(); i != p->vpDataBuffers.end(); i++) + { + if (nullptr != *i) + { +#pragma prefast(suppress:6001, "Prefast does not understand this vector will only contain validly allocated buffer pointers") + VirtualFree(*i, 0, MEM_RELEASE); + } + } + + // close files + for (auto i = p->vhTargets.begin(); i != p->vhTargets.end(); i++) + { + CloseHandle(*i); + } + + // close completion ports + if (hCompletionPort != nullptr) + { + CloseHandle(hCompletionPort); + } + + delete p; + + // notify master thread that we've finished + InterlockedDecrement(&g_lRunningThreadsCount); + + return fOk ? 1 : 0; +} + +/*****************************************************************************/ +struct ETWSessionInfo IORequestGenerator::_GetResultETWSession(const EVENT_TRACE_PROPERTIES *pTraceProperties) const +{ + struct ETWSessionInfo session = {}; + if (nullptr != pTraceProperties) + { + session.lAgeLimit = pTraceProperties->AgeLimit; + session.ulBufferSize = pTraceProperties->BufferSize; + session.ulBuffersWritten = pTraceProperties->BuffersWritten; + session.ulEventsLost = pTraceProperties->EventsLost; + session.ulFlushTimer = pTraceProperties->FlushTimer; + session.ulFreeBuffers = pTraceProperties->FreeBuffers; + session.ulLogBuffersLost = pTraceProperties->LogBuffersLost; + session.ulMaximumBuffers = pTraceProperties->MaximumBuffers; + session.ulMinimumBuffers = pTraceProperties->MinimumBuffers; + session.ulNumberOfBuffers = pTraceProperties->NumberOfBuffers; + session.ulRealTimeBuffersLost = pTraceProperties->RealTimeBuffersLost; + } + return session; +} + +DWORD IORequestGenerator::_CreateDirectoryPath(const char *pszPath) const +{ + char *c = nullptr; //variable used to browse the path + char dirPath[MAX_PATH]; //copy of the path (it will be altered) + + //only support absolute paths that specify the drive letter + if (pszPath[0] == '\0' || pszPath[1] != ':') + { + return ERROR_NOT_SUPPORTED; + } + + if (strcpy_s(dirPath, _countof(dirPath), pszPath) != 0) + { + return ERROR_BUFFER_OVERFLOW; + } + + c = dirPath; + while('\0' != *c) + { + if ('\\' == *c) + { + //skip the first one as it will be the drive name + if (c-dirPath >= 3) + { + *c = '\0'; + //create directory if it doesn't exist + if (GetFileAttributes(dirPath) == INVALID_FILE_ATTRIBUTES) + { + if (CreateDirectory(dirPath, NULL) == FALSE) + { + return GetLastError(); + } + } + *c = L'\\'; + } + } + + c++; + } + + return ERROR_SUCCESS; +} + +/*****************************************************************************/ +// create a file of the given size +// +bool IORequestGenerator::_CreateFile(UINT64 ullFileSize, const char *pszFilename, bool fZeroBuffers, bool fVerbose) const +{ + bool fSlowWrites = false; + printfv(fVerbose, "Creating file '%s' of size %I64u.\n", pszFilename, ullFileSize); + + //enable SE_MANAGE_VOLUME_NAME privilege, required to set valid size of a file + if (!SetPrivilege(SE_MANAGE_VOLUME_NAME)) + { + PrintError("WARNING: Could not set privileges for setting valid file size; will use a slower method of preparing the file\n", GetLastError()); + fSlowWrites = true; + } + + // there are various forms of paths we do not support creating subdir hierarchies + // for - relative and unc paths specifically. this is fine, and not neccesary to + // warn about. we can add support in the future. + DWORD dwError = _CreateDirectoryPath(pszFilename); + if (dwError != ERROR_SUCCESS && dwError != ERROR_NOT_SUPPORTED) + { + PrintError("WARNING: Could not create intermediate directory (error code: %u)\n", dwError); + } + + //create handle to the file + HANDLE hFile = CreateFile(pszFilename, + GENERIC_WRITE, + FILE_SHARE_WRITE, + NULL, //security + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, //flags + NULL); //template file + if (INVALID_HANDLE_VALUE == hFile) + { + PrintError("Could not create the file (error code: %u)\n", GetLastError()); + return false; + } + + if (ullFileSize > 0) + { + LARGE_INTEGER li; + li.QuadPart = ullFileSize; + + LARGE_INTEGER liNewFilePointer; + + if (!SetFilePointerEx(hFile, li, &liNewFilePointer, FILE_BEGIN)) + { + PrintError("Could not set file pointer during file creation when extending file (error code: %u)\n", GetLastError()); + CloseHandle(hFile); + return false; + } + if (liNewFilePointer.QuadPart != li.QuadPart) + { + PrintError("File pointer improperly moved during file creation when extending file\n"); + CloseHandle(hFile); + return false; + } + + //extends file (warning! this is a kind of "reservation" of space; valid size of the file is still 0!) + if (!SetEndOfFile(hFile)) + { + PrintError("Error setting end of file (error code: %u)\n", GetLastError()); + CloseHandle(hFile); + return false; + } + //try setting valid size of the file (privileges for that are enabled before CreateFile) + if (!fSlowWrites && !SetFileValidData(hFile, ullFileSize)) + { + PrintError("WARNING: Could not set valid file size (error code: %u); trying a slower method of filling the file" + " (this does not affect performance, just makes the test preparation longer)\n", + GetLastError()); + fSlowWrites = true; + } + + //if setting valid size couldn't be performed, fill in the file by simply writing to it (slower) + if (fSlowWrites) + { + li.QuadPart = 0; + if (!SetFilePointerEx(hFile, li, &liNewFilePointer, FILE_BEGIN)) + { + PrintError("Could not set file pointer during file creation (error code: %u)\n", GetLastError()); + CloseHandle(hFile); + return false; + } + if (liNewFilePointer.QuadPart != li.QuadPart) + { + PrintError("File pointer improperly moved during file creation\n"); + CloseHandle(hFile); + return false; + } + + UINT32 ulBufSize; + UINT64 ullRemainSize; + + ulBufSize = 1024*1024; + if (ullFileSize < (UINT64)ulBufSize) + { + ulBufSize = (UINT32)ullFileSize; + } + + vector vBuf(ulBufSize); + for (UINT32 i=0; i 0) + { + DWORD dwBytesWritten; + if ((UINT64)ulBufSize > ullRemainSize) + { + ulBufSize = (UINT32)ullRemainSize; + } + if (!WriteFile(hFile, &vBuf[0], ulBufSize, &dwBytesWritten, NULL)) + { + PrintError("Error while writng during file creation (error code: %u)\n", GetLastError()); + CloseHandle(hFile); + return false; + } + if (dwBytesWritten != ulBufSize) + { + PrintError("Improperly written data during file creation\n"); + CloseHandle(hFile); + return false; + } + ullRemainSize -= ulBufSize; + } + } + } + + //if compiled with debug support, check file size +#ifndef NDEBUG + LARGE_INTEGER li; + if( GetFileSizeEx(hFile, &li) ) + { + assert(li.QuadPart == (LONGLONG)ullFileSize); + } +#endif + + CloseHandle(hFile); + + return TRUE; +} + +/*****************************************************************************/ +void IORequestGenerator::_TerminateWorkerThreads(vector& vhThreads) const +{ + for (UINT32 x = 0; x < vhThreads.size(); ++x) + { + assert(NULL != vhThreads[x]); +#pragma warning( push ) +#pragma warning( disable : 6258 ) + if (!TerminateThread(vhThreads[x], 0)) + { + PrintError("Warning: unable to terminate worker thread %u\n", x); + } +#pragma warning( pop ) + } +} +/*****************************************************************************/ +void IORequestGenerator::_AbortWorkerThreads(HANDLE hStartEvent, vector& vhThreads) const +{ + assert(NULL != hStartEvent); + + if (NULL == hStartEvent) + { + return; + } + + g_bError = TRUE; + if (!SetEvent(hStartEvent)) + { + PrintError("Error signaling start event\n"); + _TerminateWorkerThreads(vhThreads); + } + else + { + //FUTURE EXTENSION: maximal timeout may be added here (and below) + while (g_lRunningThreadsCount > 0) + { + Sleep(100); + } + } +} + +/*****************************************************************************/ +bool IORequestGenerator::_StopETW(bool fUseETW, TRACEHANDLE hTraceSession) const +{ + bool fOk = true; + if (fUseETW) + { + PEVENT_TRACE_PROPERTIES pETWSession = StopETWSession(hTraceSession); + if (nullptr == pETWSession) + { + PrintError("Error stopping ETW session\n"); + fOk = false; + } + else + { + free(pETWSession); + } + } + return fOk; +} + +/*****************************************************************************/ +// initializes all global parameters +// +void IORequestGenerator::_InitializeGlobalParameters() +{ +// g_vThreadResults.clear(); // TODO: remove + g_lRunningThreadsCount = 0; //number of currently running worker threads + g_ulProcCount = 0; //number of CPUs present in the system + g_bRun = TRUE; //used for letting threads know that they should stop working + + g_bThreadError = FALSE; //true means that an error has occured in one of the threads + g_bTracing = FALSE; //true means that ETW is turned on + + _hNTDLL = nullptr; //handle to ntdll.dll + g_bError = FALSE; //true means there was fatal error during intialization and threads shouldn't perform their work + + g_pActiveGroupsAndProcs = NULL; // structure with KGroup info +} + +bool IORequestGenerator::_PrecreateFiles(Profile& profile) const +{ + bool fOk = true; + if (profile.GetPrecreateFiles() != PrecreateFiles::None) + { + vector vFilesToCreate = _GetFilesToPrecreate(profile); + vector vCreatedFiles; + for (auto file : vFilesToCreate) + { + fOk = _CreateFile(file.ullFileSize, file.sPath.c_str(), file.fZeroWriteBuffers, profile.GetVerbose()); + if (!fOk) + { + break; + } + vCreatedFiles.push_back(file.sPath); + } + + if (fOk) + { + profile.MarkFilesAsPrecreated(vCreatedFiles); + } + } + return fOk; +} + +bool IORequestGenerator::GenerateRequests(Profile& profile, IResultParser& resultParser, PRINTF pPrintOut, PRINTF pPrintError, PRINTF pPrintVerbose, struct Synchronization *pSynch, int *totalScore, double *averageLatency) +{ + g_pfnPrintOut = pPrintOut; + g_pfnPrintError = pPrintError; + g_pfnPrintVerbose = pPrintVerbose; + + bool fOk = _PrecreateFiles(profile); + if (fOk) + { + const vector& vTimeSpans = profile.GetTimeSpans(); + vector vResults(vTimeSpans.size()); + for (size_t i = 0; fOk && (i < vTimeSpans.size()); i++) + { + printfv(profile.GetVerbose(), "Generating requests for timespan %u.\n", i + 1); + fOk = _GenerateRequestsForTimeSpan(profile, vTimeSpans[i], vResults[i], pSynch); + } + + // TODO: show results only for timespans that succeeded + SystemInformation system; + string sResults = resultParser.ParseResults(profile, system, vResults); + print("%s", sResults.c_str()); + *totalScore = resultParser.GetTotalScore() * 10; + *averageLatency = resultParser.GetAverageLatency(); + } + + return fOk; +} + +bool IORequestGenerator::_GenerateRequestsForTimeSpan(const Profile& profile, const TimeSpan& timeSpan, Results& results, struct Synchronization *pSynch) +{ + //FUTURE EXTENSION: add new I/O capabilities presented in Longhorn + //FUTURE EXTENSION: add a check if the folder is compressed (cache is always enabled in case of compressed folders) + + //check if I/O request generator is already running + LONG lGenState = InterlockedExchange(&g_lGeneratorRunning, 1); + if (1 == lGenState) + { + PrintError("FATAL ERROR: I/O Request Generator already running\n"); + return false; + } + + //initialize all global parameters (in case of second run, after the first one is finished) + _InitializeGlobalParameters(); + + HANDLE hStartEvent = nullptr; // start event (used to inform the worker threads that they should start the work) + HANDLE hEndEvent = nullptr; // end event (used only in case of completin routines (not for IO Completion Ports)) + + memset(&g_EtwEventCounters, 0, sizeof(struct ETWEventCounters)); // reset all etw event counters + + //ulProcCount = getProcessorCount(); + g_pActiveGroupsAndProcs = (PACTIVE_GROUPS_AND_PROCS)malloc(sizeof(ACTIVE_GROUPS_AND_PROCS)); + if (g_pActiveGroupsAndProcs == NULL) + { + PrintError("ERROR: Memory allocation for groups and procs structure failed!\r\n"); + return false; + } + if (_GetActiveGroupsAndProcs() == false) + { + PrintError("ERROR: Failed to get groups and processors information!\r\n"); + return false; + } + + bool fUseETW = false; //true if user wants ETW + + // + // load dlls + // + assert(nullptr == _hNTDLL); + if (!_LoadDLLs()) + { + PrintError("Error loading NtQuerySystemInformation\n"); + return false; + } + + //FUTURE EXTENSION: check for conflicts in alignment (when cache is turned off only sector aligned I/O are permitted) + //FUTURE EXTENSION: check if file sizes are enough to have at least first requests not wrapping around + + vector vTargets = timeSpan.GetTargets(); + // allocate memory for random data write buffers + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + if ((i->GetRandomDataWriteBufferSize() > 0) && !i->AllocateAndFillRandomDataWriteBuffer()) + { + return false; + } + } + + // check if user wanted to create a file + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + if ((i->GetFileSize() > 0) && (i->GetPrecreated() == false)) + { + string str = i->GetPath(); + const char *filename = str.c_str(); + if (NULL == filename || NULL == *(filename)) + { + PrintError("You have to provide a filename\n"); + return false; + } + + //skip physical drives and partitions + if ('#' == filename[0] || (':' == filename[1] && 0 == filename[2])) + { + continue; + } + + //create only regular files + if (!_CreateFile(i->GetFileSize(), filename, i->GetZeroWriteBuffers(), profile.GetVerbose())) + { + return false; + } + } + } + + // get thread count + UINT32 cThreads = timeSpan.GetThreadCount(); + if (cThreads < 1) + { + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + cThreads += i->GetThreadsPerFile(); + } + } + + // allocate memory for thread handles + vector vhThreads(cThreads); + + // + // allocate memory for performance counters + // + vector vPerfInit(g_ulProcCount); + vector vPerfDone(g_ulProcCount); + vector vPerfDiff(g_ulProcCount); + + // + //create start event + // + + hStartEvent = CreateEvent(NULL, TRUE, FALSE, ""); + if (NULL == hStartEvent) + { + PrintError("Error creating the start event\n"); + return false; + } + + // + // create end event + // + if (timeSpan.GetCompletionRoutines()) + { + hEndEvent = CreateEvent(NULL, TRUE, FALSE, ""); + if (NULL == hEndEvent) + { + PrintError("Error creating the end event\n"); + return false; + } + } + + // + // create the threads + // + + g_bRun = TRUE; + WORD wGroupCtr = 0; + DWORD dwProcCtr = 0; + + volatile bool fAccountingOn = false; + UINT64 ullStartTime; //start time + UINT64 ullTimeDiff; //elapsed test time (in units returned by QueryPerformanceCounter) + vector vullSharedSequentialOffsets(vTargets.size(), 0); + + results.vThreadResults.clear(); + results.vThreadResults.resize(cThreads); + for (UINT32 iThread = 0; iThread < cThreads; ++iThread) + { + printfv(profile.GetVerbose(), "creating thread %u\n", iThread); + ThreadParameters *cookie = new ThreadParameters(); // threadFunc is going to free the memory + if (nullptr == cookie) + { + PrintError("FATAL ERROR: could not allocate memory\n"); + _AbortWorkerThreads(hStartEvent, vhThreads); + return false; + } + + UINT32 ulRelativeThreadNo = 0; + + if (timeSpan.GetThreadCount() > 0) + { + // fixed thread mode: all threads operate on all files + // and receive the entire seq index array. + // relative thread number is the same as thread number. + cookie->vTargets = vTargets; + cookie->pullSharedSequentialOffsets = &vullSharedSequentialOffsets[0]; + ulRelativeThreadNo = iThread; + } + else + { + size_t cAssignedThreads = 0; + size_t cBaseThread = 0; + auto psi = vullSharedSequentialOffsets.begin(); + for (auto i = vTargets.begin(); + i != vTargets.end(); + i++, psi++) + { + // per-file thread mode: groups of threads operate on individual files + // and receive the specific seq index for their file (note: singular). + // loop up through the targets to assign thread n to the appropriate file. + // relative thread number is file-relative, so keep track of the base + // thread number for the file and calculate relative to that. + // + // ex: two files, two threads per file + // t0: rt0 for f0 (cAssigned = 2, cBase = 0) + // t1: rt1 for f0 (cAssigned = 2, cBase = 0) + // t2: rt0 for f1 (cAssigned = 4, cBase = 2) + // t3: rt1 for f1 (cAssigned = 4, cBase = 2) + + cAssignedThreads += i->GetThreadsPerFile(); + if (iThread < cAssignedThreads) + { + cookie->vTargets.push_back(*i); + cookie->pullSharedSequentialOffsets = &(*psi); + ulRelativeThreadNo = (iThread - cBaseThread) % i->GetThreadsPerFile(); + + printfv(profile.GetVerbose(), "thread %u is relative thread %u for %s\n", iThread, ulRelativeThreadNo, i->GetPath().c_str()); + break; + } + cBaseThread += i->GetThreadsPerFile(); + } + } + + cookie->pProfile = &profile; + cookie->pTimeSpan = &timeSpan; + cookie->hStartEvent = hStartEvent; + cookie->hEndEvent = hEndEvent; + cookie->ulThreadNo = iThread; + cookie->ulRelativeThreadNo = ulRelativeThreadNo; + cookie->pfAccountingOn = &fAccountingOn; + cookie->pullStartTime = &ullStartTime; + cookie->ulRandSeed = timeSpan.GetRandSeed() + iThread; // each thread has a different random seed + + //Set thread group and proc affinity + if (timeSpan.GetGroupAffinity()) + { + cookie->wGroupNum = wGroupCtr; + cookie->dwProcNum = dwProcCtr; + + if (dwProcCtr == g_pActiveGroupsAndProcs->dwaActiveProcsCount[wGroupCtr] - 1) + { + dwProcCtr = 0; + if (wGroupCtr == g_pActiveGroupsAndProcs->wActiveGroupCount - 1) + { + wGroupCtr = 0; + } + else + { + wGroupCtr++; + } + } + else + { + dwProcCtr++; + } + } + + //create thread + cookie->pResults = &results.vThreadResults[iThread]; + + InterlockedIncrement(&g_lRunningThreadsCount); + DWORD dwThreadId; + HANDLE hThread = CreateThread(NULL, 64 * 1024, threadFunc, cookie, 0, &dwThreadId); + if (NULL == hThread) + { + //in case of error terminate running worker threads + PrintError("ERROR: unable to create thread (error code: %u)\n", GetLastError()); + InterlockedDecrement(&g_lRunningThreadsCount); + _AbortWorkerThreads(hStartEvent, vhThreads); + delete cookie; + return false; + } + + //store handle to the thread + vhThreads[iThread] = hThread; + } + // + // affinitize thread to first cpu + // (otherwise, because of bios bugs, RDTSC readings may be not accurate) + // + SetThreadAffinityMask(GetCurrentThread(), 1); //FUTURE EXTENSION: check if it is set correctly, on the end/error set the affinity (and priority) to original + + //FUTURE EXTENSION: SetPriorityClass HIGH/ABOVE_NORMAL + //FUTURE EXTENSION: lower priority so the worker threads will initialize (-2) + //FUTURE EXTENSION: raise priority so this thread will run after the time end + + if (STRUCT_SYNCHRONIZATION_SUPPORTS(pSynch, hStartEvent) && (NULL != pSynch->hStartEvent)) + { + if (WAIT_OBJECT_0 != WaitForSingleObject(pSynch->hStartEvent, INFINITE)) + { + PrintError("Error during WaitForSingleObject\n"); + _AbortWorkerThreads(hStartEvent, vhThreads); + return false; + } + } + + // + // get cycle count (it will be used to calculate actual work time) + // + DWORD dwWaitStatus = 0; + + //bAccountingOn = FALSE; // clear the accouning flag so that threads didn't count what they do while in the warmup phase + + BOOL bSynchStop = STRUCT_SYNCHRONIZATION_SUPPORTS(pSynch, hStopEvent) && (NULL != pSynch->hStopEvent); + BOOL bBreak = FALSE; + PEVENT_TRACE_PROPERTIES pETWSession = NULL; + + printfv(profile.GetVerbose(), "starting warm up...\n"); + // + // send start signal + // + if (!SetEvent(hStartEvent)) + { + PrintError("Error signaling start event\n"); + // stopETW(bUseETW, hTraceSession); + _TerminateWorkerThreads(vhThreads); //FUTURE EXTENSION: timeout for worker threads + return false; + } + + // + // wait specified amount of time in each phase (warm up, test, cool down) + // + if (timeSpan.GetWarmup() > 0) + { + if (bSynchStop) + { + assert(NULL != pSynch->hStopEvent); + dwWaitStatus = WaitForSingleObject(pSynch->hStopEvent, 1000 * timeSpan.GetWarmup()); + if (WAIT_OBJECT_0 != dwWaitStatus && WAIT_TIMEOUT != dwWaitStatus) + { + PrintError("Error during WaitForSingleObject\n"); + _TerminateWorkerThreads(vhThreads); + return false; + } + bBreak = (WAIT_TIMEOUT != dwWaitStatus); + } + else + { + Sleep(1000 * timeSpan.GetWarmup()); + } + } + + if (!bBreak) // proceed only if user didn't break the test + { + //FUTURE EXTENSION: starting ETW session shouldn't be done brutally here, should be done before warmup and here just a fast signal to start logging (see also stopping ETW session) + //FUTURE EXTENSION: put an ETW mark here, for easier parsing by external tools + + // + // start etw session + // + TRACEHANDLE hTraceSession = NULL; + if (fUseETW) + { + printfv(profile.GetVerbose(), "starting trace session\n"); + hTraceSession = StartETWSession(profile); + if (NULL == hTraceSession) + { + PrintError("Could not start ETW session\n"); + _TerminateWorkerThreads(vhThreads); + return false; + } + + if (NULL == CreateThread(NULL, 64 * 1024, etwThreadFunc, NULL, 0, NULL)) + { + PrintError("Warning: unable to create thread for ETW session\n"); + _TerminateWorkerThreads(vhThreads); + return false; + } + printfv(profile.GetVerbose(), "tracing events\n"); + } + + // + // read performance counters + // + if (_GetSystemPerfInfo(&vPerfInit[0], g_ulProcCount) == FALSE) + { + PrintError("Error reading performance counters\n"); + _StopETW(fUseETW, hTraceSession); + _TerminateWorkerThreads(vhThreads); + return false; + } + + printfv(profile.GetVerbose(), "starting measurements...\n"); + //get cycle count (it will be used to calculate actual work time) + + // + // notify the front-end that the test is about to start; + // do it before starting timing in order not to perturb measurements + // + if (STRUCT_SYNCHRONIZATION_SUPPORTS(pSynch, pfnCallbackTestStarted) && (NULL != pSynch->pfnCallbackTestStarted)) + { + pSynch->pfnCallbackTestStarted(); + } + + ullStartTime = PerfTimer::GetTime(); + +#pragma warning( push ) +#pragma warning( disable : 28931 ) + fAccountingOn = true; +#pragma warning( pop ) + + assert(timeSpan.GetDuration() > 0); + if (bSynchStop) + { + assert(NULL != pSynch->hStopEvent); + dwWaitStatus = WaitForSingleObject(pSynch->hStopEvent, 1000 * timeSpan.GetDuration()); + if (WAIT_OBJECT_0 != dwWaitStatus && WAIT_TIMEOUT != dwWaitStatus) + { + PrintError("Error during WaitForSingleObject\n"); + _StopETW(fUseETW, hTraceSession); + _TerminateWorkerThreads(vhThreads); //FUTURE EXTENSION: worker threads should have a chance to free allocated memory (see also other places calling terminateWorkerThreads()) + return FALSE; + } + bBreak = (WAIT_TIMEOUT != dwWaitStatus); + } + else + { + Sleep(1000 * timeSpan.GetDuration()); + } + + fAccountingOn = false; + + //get cycle count and perf counters + ullTimeDiff = PerfTimer::GetTime() - ullStartTime; + + // + // notify the front-end that the test has just finished; + // do it after stopping timing in order not to perturb measurements + // + if (STRUCT_SYNCHRONIZATION_SUPPORTS(pSynch, pfnCallbackTestFinished) && (NULL != pSynch->pfnCallbackTestFinished)) + { + pSynch->pfnCallbackTestFinished(); + } + + if (_GetSystemPerfInfo(&vPerfDone[0], g_ulProcCount) == FALSE) + { + PrintError("Error getting performance counters\n"); + _StopETW(fUseETW, hTraceSession); + _TerminateWorkerThreads(vhThreads); + return false; + } + + // + // stop etw session + // + if (fUseETW) + { + printfv(profile.GetVerbose(), "stopping ETW session\n"); + pETWSession = StopETWSession(hTraceSession); + if (NULL == pETWSession) + { + PrintError("Error stopping ETW session\n"); + return false; + } + else + { + free(pETWSession); + } + } + } + else + { + ullTimeDiff = 0; // mark that no test was run + } + + printfv(profile.GetVerbose(), "starting cool down...\n"); + if ((timeSpan.GetCooldown() > 0) && !bBreak) + { + if (bSynchStop) + { + assert(NULL != pSynch->hStopEvent); + dwWaitStatus = WaitForSingleObject(pSynch->hStopEvent, 1000 * timeSpan.GetCooldown()); + if (WAIT_OBJECT_0 != dwWaitStatus && WAIT_TIMEOUT != dwWaitStatus) + { + PrintError("Error during WaitForSingleObject\n"); + // stopETW(bUseETW, hTraceSession); + _TerminateWorkerThreads(vhThreads); + return false; + } + } + else + { + Sleep(1000 * timeSpan.GetCooldown()); + } + } + printfv(profile.GetVerbose(), "finished test...\n"); + + // + // signal the threads to finish + // + g_bRun = FALSE; + if (timeSpan.GetCompletionRoutines()) + { + if (!SetEvent(hEndEvent)) + { + PrintError("Error signaling end event\n"); + // stopETW(bUseETW, hTraceSession); + return false; + } + } + + // + // wait till all of the threads finish + // +#pragma warning( push ) +#pragma warning( disable : 28112 ) + while (g_lRunningThreadsCount > 0) + { + Sleep(10); //FUTURE EXTENSION: a timeout should be implemented + } +#pragma warning( pop ) + + + //check if there has been an error during threads execution + if (g_bThreadError) + { + PrintError("There has been an error during threads execution\n"); + return false; + } + + // + // close events' handles + // + CloseHandle(hStartEvent); + hStartEvent = NULL; + + if (NULL != hEndEvent) + { + CloseHandle(hEndEvent); + hEndEvent = NULL; + } + //FUTURE EXTENSION: hStartEvent and hEndEvent should be closed in case of error too + + // + // compute time spent by each cpu + // + for (unsigned int p = 0; p= vPerfInit[p].IdleTime.QuadPart); + assert(vPerfDone[p].KernelTime.QuadPart >= vPerfInit[p].KernelTime.QuadPart); + assert(vPerfDone[p].UserTime.QuadPart >= vPerfInit[p].UserTime.QuadPart); + assert(vPerfDone[p].Reserved1[0].QuadPart >= vPerfInit[p].Reserved1[0].QuadPart); + assert(vPerfDone[p].Reserved1[1].QuadPart >= vPerfInit[p].Reserved1[1].QuadPart); + assert(vPerfDone[p].Reserved2 >= vPerfInit[p].Reserved2); + + vPerfDiff[p].IdleTime.QuadPart = vPerfDone[p].IdleTime.QuadPart - vPerfInit[p].IdleTime.QuadPart; + vPerfDiff[p].KernelTime.QuadPart = vPerfDone[p].KernelTime.QuadPart - vPerfInit[p].KernelTime.QuadPart; + vPerfDiff[p].UserTime.QuadPart = vPerfDone[p].UserTime.QuadPart - vPerfInit[p].UserTime.QuadPart; + vPerfDiff[p].Reserved1[0].QuadPart = vPerfDone[p].Reserved1[0].QuadPart - vPerfInit[p].Reserved1[0].QuadPart; + vPerfDiff[p].Reserved1[1].QuadPart = vPerfDone[p].Reserved1[1].QuadPart - vPerfInit[p].Reserved1[1].QuadPart; + vPerfDiff[p].Reserved2 = vPerfDone[p].Reserved2 - vPerfInit[p].Reserved2; + } + + // + // process results and pass them to the result parser + // + + // get processors perf. info + results.vSystemProcessorPerfInfo = vPerfDiff; + results.ullTimeCount = ullTimeDiff; + + // + // create structure containing etw results and properties + // + results.fUseETW = fUseETW; + if (fUseETW) + { + results.EtwEventCounters = g_EtwEventCounters; + results.EtwSessionInfo = _GetResultETWSession(pETWSession); + + // TODO: refactor to a separate function + results.EtwMask.bProcess = profile.GetEtwProcess(); + results.EtwMask.bThread = profile.GetEtwThread(); + results.EtwMask.bImageLoad = profile.GetEtwImageLoad(); + results.EtwMask.bDiskIO = profile.GetEtwDiskIO(); + results.EtwMask.bMemoryPageFaults = profile.GetEtwMemoryPageFaults(); + results.EtwMask.bMemoryHardFaults = profile.GetEtwMemoryHardFaults(); + results.EtwMask.bNetwork = profile.GetEtwNetwork(); + results.EtwMask.bRegistry = profile.GetEtwRegistry(); + results.EtwMask.bUsePagedMemory = profile.GetEtwUsePagedMemory(); + results.EtwMask.bUsePerfTimer = profile.GetEtwUsePerfTimer(); + results.EtwMask.bUseSystemTimer = profile.GetEtwUseSystemTimer(); + results.EtwMask.bUseCyclesCounter = profile.GetEtwUseCyclesCounter(); + } + + if (g_pActiveGroupsAndProcs != nullptr) + { + free(g_pActiveGroupsAndProcs); + } + + // free memory used by random data write buffers + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->FreeRandomDataWriteBuffer(); + } + + // TODO: this won't catch error cases, which exit early + InterlockedExchange(&g_lGeneratorRunning, 0); + return true; +} + +vector IORequestGenerator::_GetFilesToPrecreate(const Profile& profile) const +{ + vector vFilesToCreate; + const vector& vTimeSpans = profile.GetTimeSpans(); + map> filesMap; + for (const auto& timeSpan : vTimeSpans) + { + vector vTargets(timeSpan.GetTargets()); + for (const auto& target : vTargets) + { + struct CreateFileParameters createFileParameters; + createFileParameters.sPath = target.GetPath(); + createFileParameters.ullFileSize = target.GetFileSize(); + createFileParameters.fZeroWriteBuffers = target.GetZeroWriteBuffers(); + + filesMap[createFileParameters.sPath].push_back(createFileParameters); + } + } + + PrecreateFiles filter = profile.GetPrecreateFiles(); + for (auto fileMapEntry : filesMap) + { + if (fileMapEntry.second.size() > 0) + { + UINT64 ullLastNonZeroSize = fileMapEntry.second[0].ullFileSize; + UINT64 ullMaxSize = fileMapEntry.second[0].ullFileSize; + bool fLastZeroWriteBuffers = fileMapEntry.second[0].fZeroWriteBuffers; + bool fHasZeroSizes = false; + bool fConstantSize = true; + bool fConstantZeroWriteBuffers = true; + for (auto file : fileMapEntry.second) + { + ullMaxSize = max(ullMaxSize, file.ullFileSize); + if (ullLastNonZeroSize == 0) + { + ullLastNonZeroSize = file.ullFileSize; + } + if (file.ullFileSize == 0) + { + fHasZeroSizes = true; + } + if ((file.ullFileSize != 0) && (file.ullFileSize != ullLastNonZeroSize)) + { + fConstantSize = false; + } + if (file.fZeroWriteBuffers != fLastZeroWriteBuffers) + { + fConstantZeroWriteBuffers = false; + } + if (file.ullFileSize != 0) + { + ullLastNonZeroSize = file.ullFileSize; + } + fLastZeroWriteBuffers = file.fZeroWriteBuffers; + } + + if (fConstantZeroWriteBuffers && ullMaxSize > 0) + { + struct CreateFileParameters file = fileMapEntry.second[0]; + file.ullFileSize = ullMaxSize; + if (filter == PrecreateFiles::UseMaxSize) + { + vFilesToCreate.push_back(file); + } + else if ((filter == PrecreateFiles::OnlyFilesWithConstantSizes) && fConstantSize && !fHasZeroSizes) + { + vFilesToCreate.push_back(file); + } + else if ((filter == PrecreateFiles::OnlyFilesWithConstantOrZeroSizes) && fConstantSize) + { + vFilesToCreate.push_back(file); + } + } + } + } + + return vFilesToCreate; +} diff --git a/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/IORequestGenerator.h b/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/IORequestGenerator.h new file mode 100644 index 0000000..f9dd5b5 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/IORequestGenerator.h @@ -0,0 +1,87 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#define INITGUID //Include this #define to use SystemTraceControlGuid in Evntrace.h. +#include //ETW +#include //ntdll.dll + + +void PrintError(const char *format, ...); +void *ManagedMalloc(size_t size); +namespace UnitTests +{ + class IORequestGeneratorUnitTests; +} + +class IORequestGenerator +{ +public: + IORequestGenerator() : + _hNTDLL(nullptr) + { + + } + + bool GenerateRequests(Profile& profile, IResultParser& resultParser, PRINTF pPrintOut, PRINTF pPrintError, PRINTF pPrintVerbose, struct Synchronization *pSynch, int *totalScore, double *averageLatency); + static UINT64 GetNextFileOffset(ThreadParameters& tp, size_t targetNum, UINT64 prevOffset); + static UINT64 GetStartingFileOffset(ThreadParameters& tp, size_t targetNum); + static UINT64 GetThreadBaseFileOffset(ThreadParameters& tp, size_t targetNum); + +private: + + struct CreateFileParameters + { + string sPath; + UINT64 ullFileSize; + bool fZeroWriteBuffers; + }; + + bool _GenerateRequestsForTimeSpan(const Profile& profile, const TimeSpan& timeSpan, Results& results, struct Synchronization *pSynch); + void _AbortWorkerThreads(HANDLE hStartEvent, vector& vhThreads) const; + void _CloseOpenFiles(vector& vhFiles) const; + DWORD _CreateDirectoryPath(const char *path) const; + bool _CreateFile(UINT64 ullFileSize, const char *pszFilename, bool fZeroBuffers, bool fVerbose) const; + void _DisplayFileSizeVerbose(bool fVerbose, UINT64 fsize) const; + bool _GetActiveGroupsAndProcs() const; + struct ETWSessionInfo _GetResultETWSession(const EVENT_TRACE_PROPERTIES *pTraceProperties) const; + bool _GetSystemPerfInfo(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION *pInfo, UINT32 uCpuCount) const; + void _InitializeGlobalParameters(); + bool _LoadDLLs(); + bool _StopETW(bool fUseETW, TRACEHANDLE hTraceSession) const; + void _TerminateWorkerThreads(vector& vhThreads) const; + bool _ValidateProfile(const Profile& profile) const; + vector _GetFilesToPrecreate(const Profile& profile) const; + void _MarkFilesAsCreated(Profile& profile, const vector& vFiles) const; + bool _PrecreateFiles(Profile& profile) const; + + HINSTANCE volatile _hNTDLL; //handle to ntdll.dll + + friend class UnitTests::IORequestGeneratorUnitTests; +}; diff --git a/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/OverlappedQueue.cpp b/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/OverlappedQueue.cpp new file mode 100644 index 0000000..23fbd36 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/OverlappedQueue.cpp @@ -0,0 +1,79 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "OverlappedQueue.h" +#include + +OverlappedQueue::OverlappedQueue(void) : + _pHead(nullptr), + _pTail(nullptr), + _cItems(0) +{ +} + +void OverlappedQueue::Add(OVERLAPPED *pOverlapped) +{ + pOverlapped->Internal = NULL; + if (_pHead == nullptr) + { + assert(_pTail == nullptr); + _pHead = pOverlapped; + } + else + { + assert(_pTail != nullptr); + _pTail->Internal = (ULONG_PTR)pOverlapped; + } + _pTail = pOverlapped; + _cItems++; +} + +bool OverlappedQueue::IsEmpty(void) const +{ + return (_pHead == nullptr); +} + +OVERLAPPED *OverlappedQueue::Remove(void) +{ + assert(!IsEmpty()); + + OVERLAPPED *pOverlapped = _pHead; + _pHead = (OVERLAPPED *)pOverlapped->Internal; + if (_pHead == nullptr) + { + _pTail = nullptr; + } + _cItems--; + return pOverlapped; +} + +size_t OverlappedQueue::GetCount() const +{ + return _cItems; +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/OverlappedQueue.h b/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/OverlappedQueue.h new file mode 100644 index 0000000..622ac67 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/OverlappedQueue.h @@ -0,0 +1,50 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#include + +// +// OverlappedQueue is a simple class that implements a queue for OVERLAPPED elements +// +class OverlappedQueue +{ +public: + OverlappedQueue(void); + + void Add(OVERLAPPED *pOverlapped); + bool IsEmpty(void) const; + OVERLAPPED * Remove(void); + size_t GetCount() const; + +private: + OVERLAPPED *_pHead; + OVERLAPPED *_pTail; + size_t _cItems; +}; \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/ThroughputMeter.cpp b/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/ThroughputMeter.cpp new file mode 100644 index 0000000..83ed6f9 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/ThroughputMeter.cpp @@ -0,0 +1,123 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "ThroughputMeter.h" + +ThroughputMeter::ThroughputMeter(void) : + _fRunning(false) +{ +} + +bool ThroughputMeter::IsRunning(void) const +{ + return _fRunning; +} + +void ThroughputMeter::Start(DWORD cBytesPerMillisecond, DWORD dwBlockSize, DWORD dwThinkTime, DWORD dwBurstSize) +{ + // Initialization + _cbCompleted = 0; + _cIO = 0; // number of completed IOs in the current burst + _cbBlockSize = dwBlockSize; + + _fThrottle = false; + _cBytesPerMillisecond = 0; + _fThink = false; + _ullDelayUntil = 0; + _thinkTime = 0; + _burstSize = 0; + _fRunning = false; + + /// GetTickCount64 -> GetTickCount + _ullStartTimestamp = GetTickCount(); + + if (0 != cBytesPerMillisecond) + { + _fThrottle = true; + _cBytesPerMillisecond = cBytesPerMillisecond; + _fRunning = true; + } + else if (0 != dwThinkTime) + { + _fThink = true; + _thinkTime = dwThinkTime; + _burstSize = dwBurstSize; + _fRunning = true; + } +} + +DWORD ThroughputMeter::GetSleepTime(void) const +{ + if (_fThink) + { + /// GetTickCount64 -> GetTickCount + ULONGLONG ullTimestamp = GetTickCount(); + if (ullTimestamp < _ullDelayUntil) + { + return (DWORD)(_ullDelayUntil - ullTimestamp); + } + else + { + return (_fThrottle) ? _GetThrottleTime() : 0; + } + } + else + { + if (_fThrottle) // think time has not been specified only check for throttling + { + return _GetThrottleTime(); + } + else + { + return 0; + } + } +} + +DWORD ThroughputMeter::_GetThrottleTime(void) const +{ + /// GetTickCount64 -> GetTickCount + ULONGLONG cbExpected = (GetTickCount() - _ullStartTimestamp) * _cBytesPerMillisecond; + return cbExpected >= (_cbCompleted + _cbBlockSize) ? 0 : 1; +} + +void ThroughputMeter::Adjust(size_t cb) +{ + _cbCompleted += cb; + _cIO++; + if (_fThink) + { + if (_cIO >= _burstSize) + { + _cIO = 0; + /// GetTickCount64 -> GetTickCount + _ullDelayUntil = GetTickCount() + _thinkTime; + } + } +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/ThroughputMeter.h b/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/ThroughputMeter.h new file mode 100644 index 0000000..0375f05 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/ThroughputMeter.h @@ -0,0 +1,61 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#include + +// ThroughputMeter class assists in metering out throughput over +// time. The meter is started by calling Start() with the throughput +// to be simulated. GetSleepTime() returns 0 when the next IO can be issued. +// Adjust() is called to notify the ThroughputMeter about how many bytes were read/written. +class ThroughputMeter +{ +public: + ThroughputMeter(void); + + bool IsRunning(void) const; + void Start(DWORD cBytesPerMillisecond, DWORD dwBlockSize, DWORD dwThinkTime, DWORD dwBurstSize); + DWORD GetSleepTime(void) const; + void Adjust(size_t cb); + +private: + DWORD _GetThrottleTime(void) const; + + bool _fRunning; // true = throughput monitoring is on + bool _fThrottle; // true = throttling is on + bool _fThink; // true = think time is enabled + ULONGLONG _cbCompleted; // completed IO + DWORD _cbBlockSize; + DWORD _cBytesPerMillisecond; // rate of throttling + ULONGLONG _ullStartTimestamp; + ULONGLONG _ullDelayUntil; // timestamp at which the next IO can be executed + DWORD _thinkTime; // time to sleep between burst of IOs + DWORD _burstSize; // number of IOs in a burst. meaningless if think time is zero + DWORD _cIO; // count of IOs in the current burst +}; \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/etw.cpp b/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/etw.cpp new file mode 100644 index 0000000..445c3f4 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/etw.cpp @@ -0,0 +1,502 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "etw.h" +#include "common.h" +#include +#include + +#include //WNODE_HEADER + +#define INITGUID //Include this #define to use SystemTraceControlGuid in Evntrace.h. +#include //ETW + +#include "IORequestGenerator.h" + +// variables declared in IORequestGenerator.cpp +extern struct ETWEventCounters g_EtwEventCounters; +extern BOOL volatile g_bTracing; + + +DEFINE_GUID ( /* 3d6fa8d4-fe05-11d0-9dda-00c04fd7ba7c */ + DiskIoGuid, + 0x3d6fa8d4, + 0xfe05, + 0x11d0, + 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c + ); + +DEFINE_GUID ( /* 90cbdc39-4a3e-11d1-84f4-0000f80464e3 */ + FileIoGuid, + 0x90cbdc39, + 0x4a3e, + 0x11d1, + 0x84, 0xf4, 0x00, 0x00, 0xf8, 0x04, 0x64, 0xe3 + ); + +DEFINE_GUID ( /* 2cb15d1d-5fc1-11d2-abe1-00a0c911f518 */ + ImageLoadGuid, + 0x2cb15d1d, + 0x5fc1, + 0x11d2, + 0xab, 0xe1, 0x00, 0xa0, 0xc9, 0x11, 0xf5, 0x18 + ); + +DEFINE_GUID ( /* 3d6fa8d3-fe05-11d0-9dda-00c04fd7ba7c */ + PageFaultGuid, + 0x3d6fa8d3, + 0xfe05, + 0x11d0, + 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c + ); + +DEFINE_GUID ( /* 9a280ac0-c8e0-11d1-84e2-00c04fb998a2 */ + TcpIpGuid, + 0x9a280ac0, + 0xc8e0, + 0x11d1, + 0x84, 0xe2, 0x00, 0xc0, 0x4f, 0xb9, 0x98, 0xa2 + ); + +DEFINE_GUID ( /* bf3a50c5-a9c9-4988-a005-2df0b7c80f80 */ + UdpIpGuid, + 0xbf3a50c5, + 0xa9c9, + 0x4988, + 0xa0, 0x05, 0x2d, 0xf0, 0xb7, 0xc8, 0x0f, 0x80 + ); + +DEFINE_GUID ( /* 3d6fa8d0-fe05-11d0-9dda-00c04fd7ba7c */ + ProcessGuid, + 0x3d6fa8d0, + 0xfe05, + 0x11d0, + 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c + ); + +DEFINE_GUID ( /* AE53722E-C863-11d2-8659-00C04FA321A1 */ + RegistryGuid, + 0xae53722e, + 0xc863, + 0x11d2, + 0x86, 0x59, 0x0, 0xc0, 0x4f, 0xa3, 0x21, 0xa1 +); + +DEFINE_GUID ( /* 3d6fa8d1-fe05-11d0-9dda-00c04fd7ba7c */ + ThreadGuid, + 0x3d6fa8d1, + 0xfe05, + 0x11d0, + 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c + ); + +/*****************************************************************************/ +// consumes events' data +// +BOOL TraceEvents() +{ + TRACEHANDLE handles[1]; + EVENT_TRACE_LOGFILE logfile; + + memset(&logfile, 0, sizeof(EVENT_TRACE_LOGFILE)); + + logfile.LoggerName = KERNEL_LOGGER_NAME; + logfile.LogFileName = NULL; + logfile.LogFileMode = EVENT_TRACE_REAL_TIME_MODE; + + logfile.IsKernelTrace = true; + + handles[0] = OpenTrace(&logfile); + if( (TRACEHANDLE)INVALID_HANDLE_VALUE == handles[0] ) + { + PrintError("ETW ERROR: OpenTrace failed (error code: %d)\n", GetLastError()); + return false; + } + else + { + ProcessTrace(handles, 1, 0, 0); + CloseTrace(handles[0]); + } + + return true; +} + +/*****************************************************************************/ +// allocates memory for ETW properties structure +// +PEVENT_TRACE_PROPERTIES allocateEventTraceProperties() +{ + PEVENT_TRACE_PROPERTIES pProperties = NULL; + size_t size = 0; + + + size = sizeof(EVENT_TRACE_PROPERTIES)+sizeof(KERNEL_LOGGER_NAME); + pProperties = (PEVENT_TRACE_PROPERTIES)malloc(size); + if( NULL == pProperties ) + { + PrintError("FATAL ERROR: unable to allocate memory (error code: %d)\n", GetLastError()); + return NULL; + } + + memset(pProperties, 0, size); + + pProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); + pProperties->Wnode.BufferSize = (ULONG)size; + pProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID; + + strcpy_s((char *)pProperties+pProperties->LoggerNameOffset, + size-pProperties->LoggerNameOffset, KERNEL_LOGGER_NAME); + + return pProperties; +} + +/*****************************************************************************/ +// callback function for disk I/O events +void WINAPI eventDiskIo(PEVENT_TRACE pEvent) +{ + if( EVENT_TRACE_TYPE_IO_READ == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullIORead; + } + else if( EVENT_TRACE_TYPE_IO_WRITE == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullIOWrite; + } +} + +/*****************************************************************************/ +// callback function for LoadImage events +// +void WINAPI eventLoadImage(PEVENT_TRACE pEvent) +{ + if( EVENT_TRACE_TYPE_LOAD == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullImageLoad; + } +} + +/*****************************************************************************/ +// callback function for memory events +// +void WINAPI eventPageFault(PEVENT_TRACE pEvent) +{ + if( EVENT_TRACE_TYPE_MM_COW == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullMMCopyOnWrite; + } + else if( EVENT_TRACE_TYPE_MM_DZF == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullMMDemandZeroFault; + } + else if( EVENT_TRACE_TYPE_MM_GPF == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullMMGuardPageFault; + } + else if( EVENT_TRACE_TYPE_MM_HPF == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullMMHardPageFault; + } + else if( EVENT_TRACE_TYPE_MM_TF == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullMMTransitionFault; + } +} + +/*****************************************************************************/ +// callback function for network and TCP/IP events +// +void WINAPI eventTcpIp(PEVENT_TRACE pEvent) +{ + if( EVENT_TRACE_TYPE_ACCEPT == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullNetAccept; + } + else if( EVENT_TRACE_TYPE_CONNECT == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullNetConnect; + } + else if( EVENT_TRACE_TYPE_DISCONNECT == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullNetDisconnect; + } + else if( EVENT_TRACE_TYPE_RECEIVE == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullNetTcpReceive; + } + else if( EVENT_TRACE_TYPE_RECONNECT == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullNetReconnect; + } + else if( EVENT_TRACE_TYPE_RETRANSMIT == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullNetRetransmit; + } + else if( EVENT_TRACE_TYPE_SEND == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullNetTcpSend; + } +} + +/*****************************************************************************/ +// callback function for UDP/IP events +// +void WINAPI eventUdpIp(PEVENT_TRACE pEvent) +{ + if( EVENT_TRACE_TYPE_SEND == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullNetUdpSend; + } + else if( EVENT_TRACE_TYPE_RECEIVE == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullNetUdpReceive; + } +} + +/*****************************************************************************/ +// callback function for thread events +// +void WINAPI eventThread(PEVENT_TRACE pEvent) +{ + if( EVENT_TRACE_TYPE_START == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullThreadStart; + } + else if( EVENT_TRACE_TYPE_END == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullThreadEnd; + } +} + +/*****************************************************************************/ +// callback function for process events +// +void WINAPI eventProcess(PEVENT_TRACE pEvent) +{ + if( EVENT_TRACE_TYPE_START == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullProcessStart; + } + else if( EVENT_TRACE_TYPE_END == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullProcessEnd; + } +} + +/*****************************************************************************/ +// callback function for registry events +// +void WINAPI eventRegistry(PEVENT_TRACE pEvent) +{ + if( EVENT_TRACE_TYPE_REGCREATE == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegCreate; + } + else if( EVENT_TRACE_TYPE_REGDELETE == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegDelete; + } + else if( EVENT_TRACE_TYPE_REGDELETEVALUE == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegDeleteValue; + } + else if( EVENT_TRACE_TYPE_REGENUMERATEKEY == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegEnumerateKey; + } + else if( EVENT_TRACE_TYPE_REGENUMERATEVALUEKEY == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegEnumerateValueKey; + } + else if( EVENT_TRACE_TYPE_REGFLUSH == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegFlush; + } +// else if( EVENT_TRACE_TYPE_REGKCBDMP == pEvent->Header.Class.Type ) +// { +// ++ETWEventCounters.ullRegKcbDmp; +// } + else if( EVENT_TRACE_TYPE_REGOPEN == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegOpen; + } + else if( EVENT_TRACE_TYPE_REGQUERY == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegQuery; + } + else if( EVENT_TRACE_TYPE_REGQUERYMULTIPLEVALUE == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegQueryMultipleValue; + } + else if( EVENT_TRACE_TYPE_REGQUERYVALUE == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegQueryValue; + } + else if( EVENT_TRACE_TYPE_REGSETINFORMATION == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegSetInformation; + } + else if( EVENT_TRACE_TYPE_REGSETVALUE == pEvent->Header.Class.Type ) + { + ++g_EtwEventCounters.ullRegSetValue; + } +} + +/*****************************************************************************/ +// starts ETW session and sets callback functions for various groups of events +// +TRACEHANDLE StartETWSession(const Profile& profile) +{ + PEVENT_TRACE_PROPERTIES pProperties; + + pProperties = allocateEventTraceProperties(); + if (nullptr == pProperties) + { + return 0; + } + + pProperties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; + + //use paged memory + if (profile.GetEtwUsePagedMemory()) + { + pProperties->LogFileMode |= EVENT_TRACE_USE_PAGED_MEMORY; + } + + // + // event classes + // + if (profile.GetEtwProcess()) + { + pProperties->EnableFlags |= EVENT_TRACE_FLAG_PROCESS; + SetTraceCallback(&ProcessGuid, eventThread); + } + + if (profile.GetEtwThread()) + { + pProperties->EnableFlags |= EVENT_TRACE_FLAG_THREAD; + SetTraceCallback(&ThreadGuid, eventThread); + } + + if (profile.GetEtwImageLoad()) + { + pProperties->EnableFlags |= EVENT_TRACE_FLAG_IMAGE_LOAD; + SetTraceCallback(&ImageLoadGuid, eventLoadImage); + } + + if (profile.GetEtwDiskIO()) + { + pProperties->EnableFlags |= EVENT_TRACE_FLAG_DISK_IO; + SetTraceCallback(&DiskIoGuid, eventDiskIo); + } + + if (profile.GetEtwMemoryPageFaults()) + { + pProperties->EnableFlags |= EVENT_TRACE_FLAG_MEMORY_PAGE_FAULTS; + SetTraceCallback(&PageFaultGuid, eventPageFault); + } + + if (profile.GetEtwMemoryHardFaults()) + { + pProperties->EnableFlags |= EVENT_TRACE_FLAG_MEMORY_HARD_FAULTS; + SetTraceCallback(&PageFaultGuid, eventPageFault); + } + + if (profile.GetEtwNetwork()) + { + pProperties->EnableFlags |= EVENT_TRACE_FLAG_NETWORK_TCPIP; + SetTraceCallback(&TcpIpGuid, eventTcpIp); + SetTraceCallback(&UdpIpGuid, eventUdpIp); + } + + if (profile.GetEtwRegistry()) + { + pProperties->EnableFlags |= EVENT_TRACE_FLAG_REGISTRY; + SetTraceCallback(&RegistryGuid, eventRegistry); + } + + // + // timer + // + if (profile.GetEtwUsePerfTimer()) + { + pProperties->Wnode.ClientContext = 1; + } + if (profile.GetEtwUseSystemTimer()) + { + pProperties->Wnode.ClientContext = 2; + } + if (profile.GetEtwUseCyclesCounter()) + { + pProperties->Wnode.ClientContext = 3; + } + + pProperties->Wnode.Guid = SystemTraceControlGuid; + + TRACEHANDLE hTraceSession; + ULONG ret = StartTrace(&hTraceSession, KERNEL_LOGGER_NAME, pProperties); + free(pProperties); + if (ERROR_SUCCESS != ret) + { + PrintError("Error starting trace session\n"); + return 0; + } + + return hTraceSession; +} + +/*****************************************************************************/ +// stops ETW session +// +PEVENT_TRACE_PROPERTIES StopETWSession(TRACEHANDLE hTraceSession) +{ + PEVENT_TRACE_PROPERTIES pProperties; + + pProperties = allocateEventTraceProperties(); + if( NULL == pProperties ) + { + return NULL; + } + + ULONG ret; + + ret = ControlTrace(hTraceSession, NULL, pProperties, EVENT_TRACE_CONTROL_STOP); + if( ERROR_SUCCESS != ret ) + { + PrintError("Error stopping trace session\n"); + return NULL; + } + + //wait + while(g_bTracing) + { + Sleep(10); // TODO: remove active waiting + } + + return pProperties; +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/etw.h b/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/etw.h new file mode 100644 index 0000000..4043e35 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/IORequestGenerator/etw.h @@ -0,0 +1,41 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + + +#include +#include ///WNODE_HEADER +#define INITGUID //Include this #define to use SystemTraceControlGuid in Evntrace.h. +#include //ETW +#include "..\Common\Common.h" + +BOOL TraceEvents(); +TRACEHANDLE StartETWSession(const Profile& profile); +PEVENT_TRACE_PROPERTIES StopETWSession(TRACEHANDLE hTraceSession); \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/LICENSE b/CristalDiskMark/source/diskspd2_0_15a/LICENSE new file mode 100644 index 0000000..cd7af07 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/CristalDiskMark/source/diskspd2_0_15a/LICENSE.txt b/CristalDiskMark/source/diskspd2_0_15a/LICENSE.txt new file mode 100644 index 0000000..b4731ac --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/CristalDiskMark/source/diskspd2_0_15a/README.md b/CristalDiskMark/source/diskspd2_0_15a/README.md new file mode 100644 index 0000000..d3b8582 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/README.md @@ -0,0 +1,14 @@ +DISKSPD +======= + +DISKSPD is a storage load generator / performance test tool from the Windows/Windows Server and Cloud Server Infrastructure Engineering teams. Please see the included documentation (docx and pdf formats). + +Compilation is supported with Visual Studio and Visual Studio Express. Use the Visual Studio solution file inside the diskspd_vs2013 directory. + +Source code is hosted at the following repo, if you did not clone it directly: + +[https://github.com/microsoft/diskspd](https://github.com/microsoft/diskspd "https://github.com/microsoft/diskspd") + +A binary release is hosted by Microsoft at the following location: + +[http://aka.ms/diskspd](http://aka.ms/diskspd "http://aka.ms/diskspd") diff --git a/CristalDiskMark/source/diskspd2_0_15a/ResultParser/ResultParser.cpp b/CristalDiskMark/source/diskspd2_0_15a/ResultParser/ResultParser.cpp new file mode 100644 index 0000000..1719bc9 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/ResultParser/ResultParser.cpp @@ -0,0 +1,848 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +// ResultParser.cpp : Defines the entry point for the DLL application. +// +#include "ResultParser.h" + +#include "common.h" + +#include +#include +#include //ntdll.dll + +#include //WNODE_HEADER +#include + +#include + +// TODO: refactor to a single function shared with the XmlResultParser +void ResultParser::_Print(const char *format, ...) +{ + assert(nullptr != format); + va_list listArg; + va_start(listArg, format); + char buffer[4096] = {}; + vsprintf_s(buffer, _countof(buffer), format, listArg); + va_end(listArg); + _sResult += buffer; +} + +/*****************************************************************************/ +// display file size in a user-friendly form +// + +void ResultParser::_DisplayFileSize(UINT64 fsize) +{ + if( fsize > (UINT64)10*1024*1024*1024 ) // > 10GB + { + _Print("%uGB", fsize >> 30); + } + else if( fsize > (UINT64)10*1024*1024 ) // > 10MB + { + _Print("%uMB", fsize >> 20); + } + else if( fsize > 10*1024 ) // > 10KB + { + _Print("%uKB", fsize >> 10); + } + else + { + _Print("%I64uB", fsize); + } +} + +/*****************************************************************************/ +void ResultParser::_DisplayETWSessionInfo(struct ETWSessionInfo sessionInfo) +{ + _Print("\n\n"); + _Print(" ETW Buffer Settings & Statistics\n"); + _Print("--------------------------------------------------------\n"); + _Print("(KB) Buffers (Secs) (Mins)\n"); + _Print("Size | Min | Max | Free | Written | Flush Age\n"); + + _Print("%-5lu %5lu %-5lu %-2lu %8lu %8lu %8d\n\n", + sessionInfo.ulBufferSize, + sessionInfo.ulMinimumBuffers, + sessionInfo.ulMaximumBuffers, + sessionInfo.ulFreeBuffers, + sessionInfo.ulBuffersWritten, + sessionInfo.ulFlushTimer, + sessionInfo.lAgeLimit); + + _Print("Allocated Buffers: %lu\n", + sessionInfo.ulNumberOfBuffers); + + _Print("LOST EVENTS:%15lu\n", + sessionInfo.ulEventsLost); + + _Print("LOST LOG BUFFERS:%10lu\n", + sessionInfo.ulLogBuffersLost); + + _Print("LOST REAL TIME BUFFERS:%4lu\n", + sessionInfo.ulRealTimeBuffersLost); +} + + +/*****************************************************************************/ +void ResultParser::_DisplayETW(struct ETWMask ETWMask, struct ETWEventCounters EtwEventCounters) +{ + _Print("\n\n\nETW:\n"); + _Print("----\n\n"); + + if (ETWMask.bDiskIO) + { + _Print("\tDisk I/O\n"); + + _Print("\t\tRead: %I64u\n", EtwEventCounters.ullIORead); + _Print("\t\tWrite: %I64u\n", EtwEventCounters.ullIOWrite); + } + if (ETWMask.bImageLoad) + { + _Print("\tLoad Image\n"); + + _Print("\t\tLoad Image: %I64u\n", EtwEventCounters.ullImageLoad); + } + if (ETWMask.bMemoryPageFaults) + { + _Print("\tMemory Page Faults\n"); + + _Print("\t\tCopy on Write: %I64u\n", EtwEventCounters.ullMMCopyOnWrite); + _Print("\t\tDemand Zero fault: %I64u\n", EtwEventCounters.ullMMDemandZeroFault); + _Print("\t\tGuard Page fault: %I64u\n", EtwEventCounters.ullMMGuardPageFault); + _Print("\t\tHard page fault: %I64u\n", EtwEventCounters.ullMMHardPageFault); + _Print("\t\tTransition fault: %I64u\n", EtwEventCounters.ullMMTransitionFault); + } + if (ETWMask.bMemoryHardFaults && !ETWMask.bMemoryPageFaults ) + { + _Print("\tMemory Hard Faults\n"); + _Print("\t\tHard page fault: %I64u\n", EtwEventCounters.ullMMHardPageFault); + } + if (ETWMask.bNetwork) + { + _Print("\tNetwork\n"); + + _Print("\t\tAccept: %I64u\n", EtwEventCounters.ullNetAccept); + _Print("\t\tConnect: %I64u\n", EtwEventCounters.ullNetConnect); + _Print("\t\tDisconnect: %I64u\n", EtwEventCounters.ullNetDisconnect); + _Print("\t\tReconnect: %I64u\n", EtwEventCounters.ullNetReconnect); + _Print("\t\tRetransmit: %I64u\n", EtwEventCounters.ullNetRetransmit); + _Print("\t\tTCP/IP Send: %I64u\n", EtwEventCounters.ullNetTcpSend); + _Print("\t\tTCP/IP Receive: %I64u\n", EtwEventCounters.ullNetTcpReceive); + _Print("\t\tUDP/IP Send: %I64u\n", EtwEventCounters.ullNetUdpSend); + _Print("\t\tUDP/IP Receive: %I64u\n", EtwEventCounters.ullNetUdpReceive); + } + if (ETWMask.bProcess) + { + _Print("\tProcess\n"); + + _Print("\t\tStart: %I64u\n", EtwEventCounters.ullProcessStart); + _Print("\t\tEnd: %I64u\n", EtwEventCounters.ullProcessEnd); + } + if (ETWMask.bRegistry) + { + _Print("\tRegistry\n"); + + _Print("\t\tNtCreateKey: %I64u\n", + EtwEventCounters.ullRegCreate); + + _Print("\t\tNtDeleteKey: %I64u\n", + EtwEventCounters.ullRegDelete); + + _Print("\t\tNtDeleteValueKey: %I64u\n", + EtwEventCounters.ullRegDeleteValue); + + _Print("\t\tNtEnumerateKey: %I64u\n", + EtwEventCounters.ullRegEnumerateKey); + + _Print("\t\tNtEnumerateValueKey: %I64u\n", + EtwEventCounters.ullRegEnumerateValueKey); + + _Print("\t\tNtFlushKey: %I64u\n", + EtwEventCounters.ullRegFlush); + + _Print("\t\tKcbDump/create: %I64u\n", + EtwEventCounters.ullRegKcbDmp); + + _Print("\t\tNtOpenKey: %I64u\n", + EtwEventCounters.ullRegOpen); + + _Print("\t\tNtQueryKey: %I64u\n", + EtwEventCounters.ullRegQuery); + + _Print("\t\tNtQueryMultipleValueKey: %I64u\n", + EtwEventCounters.ullRegQueryMultipleValue); + + _Print("\t\tNtQueryValueKey: %I64u\n", + EtwEventCounters.ullRegQueryValue); + + _Print("\t\tNtSetInformationKey: %I64u\n", + EtwEventCounters.ullRegSetInformation); + + _Print("\t\tNtSetValueKey: %I64u\n", + EtwEventCounters.ullRegSetValue); + } + if (ETWMask.bThread) + { + _Print("\tThread\n"); + + _Print("\t\tStart: %I64u\n", EtwEventCounters.ullThreadStart); + _Print("\t\tEnd: %I64u\n", EtwEventCounters.ullThreadEnd); + } +} + +void ResultParser::_PrintTarget(const Target &target, bool fUseThreadsPerFile, bool fCompletionRoutines) +{ + _Print("\tpath: '%s'\n", target.GetPath().c_str()); + _Print("\t\tthink time: %ums\n", target.GetThinkTime()); + _Print("\t\tburst size: %u\n", target.GetBurstSize()); + // TODO: completion routines/ports + + if (target.GetDisableAllCache()) + { + _Print("\t\tsoftware and hardware write cache disabled\n"); + } + else if (target.GetDisableOSCache()) + { + _Print("\t\tsoftware cache disabled\n"); + } + + if (!target.GetDisableAllCache() && !target.GetDisableOSCache()) + { + _Print("\t\tusing software and hardware write cache\n"); + } + + if (target.GetZeroWriteBuffers()) + { + _Print("\t\tzeroing write buffers\n"); + } + + if (target.GetRandomDataWriteBufferSize() > 0) + { + _Print("\t\twrite buffer size: %I64u\n", target.GetRandomDataWriteBufferSize()); + string sWriteBufferSourcePath = target.GetRandomDataWriteBufferSourcePath(); + if (sWriteBufferSourcePath != "") + { + _Print("\t\twrite buffer source: '%s'\n", sWriteBufferSourcePath.c_str()); + } + } + + if (target.GetUseParallelAsyncIO()) + { + _Print("\t\tusing parallel async I/O\n"); + } + + if (target.GetWriteRatio() == 0) + { + _Print("\t\tperforming read test\n"); + } + else if (target.GetWriteRatio() == 100) + { + _Print("\t\tperforming write test\n"); + } + else + { + _Print("\t\tperforming mix test (write/read ratio: %d/100)\n", target.GetWriteRatio()); + } + _Print("\t\tblock size: %d\n", target.GetBlockSizeInBytes()); + if (target.GetUseRandomAccessPattern()) + { + _Print("\t\tusing random I/O (alignment: "); + } + else + { + if (target.GetUseInterlockedSequential()) + { + _Print("\t\tusing interlocked sequential I/O (stride: "); + } + else + { + _Print("\t\tusing sequential I/O (stride: "); + } + } + _Print("%I64u)\n", target.GetBlockAlignmentInBytes()); + + _Print("\t\tnumber of outstanding I/O operations: %d\n", target.GetRequestCount()); + if (0 != target.GetBaseFileOffsetInBytes()) + { + _Print("\t\tbase file offset: %I64u\n", target.GetBaseFileOffsetInBytes()); + } + + if (0 != target.GetMaxFileSize()) + { + _Print("\t\tmax file size: %I64u\n", target.GetMaxFileSize()); + } + + _Print("\t\tthread stride size: %I64u\n", target.GetThreadStrideInBytes()); + + if (target.GetSequentialScanHint()) + { + _Print("\t\tusing FILE_FLAG_SEQUENTIAL_SCAN hint\n"); + } + + if (target.GetRandomAccessHint()) + { + _Print("\t\tusing FILE_FLAG_RANDOM_ACCESS hint\n"); + } + + if (fUseThreadsPerFile) + { + _Print("\t\tthreads per file: %d\n", target.GetThreadsPerFile()); + } + if (target.GetRequestCount() > 1 && fUseThreadsPerFile) + { + if (fCompletionRoutines) + { + _Print("\t\tusing completion routines (ReadFileEx/WriteFileEx)\n"); + } + else + { + _Print("\t\tusing I/O Completion Ports\n"); + } + } + + if (target.GetIOPriorityHint() == IoPriorityHintVeryLow) + { + _Print("\t\tIO priority: very low\n"); + } + else if (target.GetIOPriorityHint() == IoPriorityHintLow) + { + _Print("\t\tIO priority: low\n"); + } + else if (target.GetIOPriorityHint() == IoPriorityHintNormal) + { + _Print("\t\tIO priority: normal\n"); + } + else + { + _Print("\t\tIO priority: unknown\n"); + } +} + +void ResultParser::_PrintTimeSpan(const TimeSpan& timeSpan) +{ + _Print("\tduration: %us\n", timeSpan.GetDuration()); + _Print("\twarm up time: %us\n", timeSpan.GetWarmup()); + _Print("\tcool down time: %us\n", timeSpan.GetCooldown()); + if (timeSpan.GetDisableAffinity()) + { + _Print("\taffinity disabled\n"); + } + if (timeSpan.GetMeasureLatency()) + { + _Print("\tmeasuring latency\n"); + } + if (timeSpan.GetCalculateIopsStdDev()) + { + _Print("\tcalculating IOPS stddev with bucket duration = %u milliseconds\n", timeSpan.GetIoBucketDurationInMilliseconds()); + } + _Print("\trandom seed: %u\n", timeSpan.GetRandSeed()); + + vector vAffinity = timeSpan.GetAffinityAssignments(); + if ( vAffinity.size() > 0) + { + _Print("\tadvanced affinity: "); + for (unsigned int x = 0; x < vAffinity.size(); ++x) + { + _Print("%d", vAffinity[x]); + if (x < vAffinity.size() - 1) + { + _Print(", "); + } + } + _Print("\n"); + } + + vector vTargets(timeSpan.GetTargets()); + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + _PrintTarget(*i, (timeSpan.GetThreadCount() == 0), timeSpan.GetCompletionRoutines()); + } +} + +void ResultParser::_PrintProfile(const Profile& profile) +{ + _Print("\nCommand Line: %s\n", profile.GetCmdLine().c_str()); + _Print("\n"); + _Print("Input parameters:\n\n"); + if (profile.GetVerbose()) + { + _Print("\tusing verbose mode\n"); + } + + const vector& vTimeSpans = profile.GetTimeSpans(); + int c = 1; + for (auto i = vTimeSpans.begin(); i != vTimeSpans.end(); i++) + { + _Print("\ttimespan: %3d\n", c++); + _Print("\t-------------\n"); + _PrintTimeSpan(*i); + _Print("\n"); + } +} + +void ResultParser::_PrintCpuUtilization(const Results& results) +{ + size_t ulProcCount = results.vSystemProcessorPerfInfo.size(); + double fTime = PerfTimer::PerfTimeToSeconds(results.ullTimeCount); + + char szFloatBuffer[1024]; + + _Print("\nCPU | Usage | User | Kernel | Idle\n"); + _Print("-------------------------------------------\n"); + + double busyTime = 0; + double totalIdleTime = 0; + double totalUserTime = 0; + double totalKrnlTime = 0; + + for (unsigned int x = 0; x totalLatencyHistogram; + IoBucketizer totalIoBucketizer; + + _PrintSectionFieldNames(timeSpan); + + _PrintSectionBorderLine(timeSpan); + + for (unsigned int iThread = 0; iThread < results.vThreadResults.size(); ++iThread) + { + const ThreadResults& threadResults = results.vThreadResults[iThread]; + for (unsigned int iFile = 0; iFile < threadResults.vTargetResults.size(); iFile++) + { + const TargetResults& targetResults = threadResults.vTargetResults[iFile]; + + UINT64 ullBytesCount = 0; + UINT64 ullIOCount = 0; + + Histogram latencyHistogram; + IoBucketizer ioBucketizer; + + if ((section == _SectionEnum::WRITE) || (section == _SectionEnum::TOTAL)) + { + ullBytesCount += targetResults.ullWriteBytesCount; + ullIOCount += targetResults.ullWriteIOCount; + + if (timeSpan.GetMeasureLatency()) + { + latencyHistogram.Merge(targetResults.writeLatencyHistogram); + totalLatencyHistogram.Merge(targetResults.writeLatencyHistogram); + } + + if (timeSpan.GetCalculateIopsStdDev()) + { + ioBucketizer.Merge(targetResults.writeBucketizer); + totalIoBucketizer.Merge(targetResults.writeBucketizer); + } + } + + if ((section == _SectionEnum::READ) || (section == _SectionEnum::TOTAL)) + { + ullBytesCount += targetResults.ullReadBytesCount; + ullIOCount += targetResults.ullReadIOCount; + + if (timeSpan.GetMeasureLatency()) + { + latencyHistogram.Merge(targetResults.readLatencyHistogram); + totalLatencyHistogram.Merge(targetResults.readLatencyHistogram); + } + + if (timeSpan.GetCalculateIopsStdDev()) + { + ioBucketizer.Merge(targetResults.readBucketizer); + totalIoBucketizer.Merge(targetResults.readBucketizer); + } + } + + _Print("%6u | %15llu | %12llu | %10.2f | %10.2f", + iThread, + ullBytesCount, + ullIOCount, + (double) ullBytesCount / 1024 / 1024 / fTime, + (double) ullIOCount / fTime); + + if (timeSpan.GetMeasureLatency()) + { + double avgLat = latencyHistogram.GetAvg() / 1000; + _Print(" | %8.3f", avgLat); + } + + if (timeSpan.GetCalculateIopsStdDev()) + { + double iopsStdDev = ioBucketizer.GetStandardDeviation() / fBucketTime; + _Print(" | %10.2f", iopsStdDev); + } + + if (timeSpan.GetMeasureLatency()) + { + if (latencyHistogram.GetSampleSize() > 0) + { + double latStdDev = latencyHistogram.GetStandardDeviation() / 1000; + _Print(" | %8.3f", latStdDev); + } + else + { + _Print(" | N/A"); + } + } + + _Print(" | %s (", targetResults.sPath.c_str()); + + _DisplayFileSize(targetResults.ullFileSize); + _Print(")\n"); + + ullTotalBytesCount += ullBytesCount; + ullTotalIOCount += ullIOCount; + } + } + + _PrintSectionBorderLine(timeSpan); + + double totalAvgLat = 0; + + if (timeSpan.GetMeasureLatency()) + { + totalAvgLat = totalLatencyHistogram.GetAvg() / 1000; + } + + _Print("total: %15llu | %12llu | %10.2f | %10.2f", + ullTotalBytesCount, + ullTotalIOCount, + (double) ullTotalBytesCount / 1024 / 1024 / fTime, + (double) ullTotalIOCount / fTime); + + + if (section == _SectionEnum::TOTAL) + { + _totalScore = (int)(ullTotalBytesCount / 1000 / fTime); + } + + if (timeSpan.GetMeasureLatency()) + { + _Print(" | %8.3f", totalAvgLat); + if (section == _SectionEnum::TOTAL) + { + _averageLatency = totalAvgLat; + } + } + + if (timeSpan.GetCalculateIopsStdDev()) + { + double iopsStdDev = totalIoBucketizer.GetStandardDeviation() / fBucketTime; + _Print(" | %10.2f", iopsStdDev); + } + + if (timeSpan.GetMeasureLatency()) + { + if (totalLatencyHistogram.GetSampleSize() > 0) + { + double latStdDev = totalLatencyHistogram.GetStandardDeviation() / 1000; + _Print(" | %8.3f", latStdDev); + } + else + { + _Print(" | N/A"); + } + } + + _Print("\n"); +} + +void ResultParser::_PrintLatencyPercentiles(const Results& results) +{ + Histogram readLatencyHistogram; + Histogram writeLatencyHistogram; + Histogram totalLatencyHistogram; + + for (const auto& thread : results.vThreadResults) + { + for (const auto& target : thread.vTargetResults) + { + readLatencyHistogram.Merge(target.readLatencyHistogram); + + writeLatencyHistogram.Merge(target.writeLatencyHistogram); + + totalLatencyHistogram.Merge(target.writeLatencyHistogram); + totalLatencyHistogram.Merge(target.readLatencyHistogram); + } + } + + bool fHasReads = readLatencyHistogram.GetSampleSize() > 0; + bool fHasWrites = writeLatencyHistogram.GetSampleSize() > 0; + + _Print(" %%-ile | Read (ms) | Write (ms) | Total (ms)\n"); + _Print("----------------------------------------------\n"); + + string readMin = + fHasReads ? + Util::DoubleToStringHelper(readLatencyHistogram.GetMin()/1000) : + "N/A"; + + string writeMin = + fHasWrites ? + Util::DoubleToStringHelper(writeLatencyHistogram.GetMin() / 1000) : + "N/A"; + + _Print(" min | %10s | %10s | %10.3lf\n", + readMin.c_str(), writeMin.c_str(), totalLatencyHistogram.GetMin()/1000); + + PercentileDescriptor percentiles[] = + { + { 0.25, "25th" }, + { 0.50, "50th" }, + { 0.75, "75th" }, + { 0.90, "90th" }, + { 0.95, "95th" }, + { 0.99, "99th" }, + { 0.999, "3-nines" }, + { 0.9999, "4-nines" }, + { 0.99999, "5-nines" }, + { 0.999999, "6-nines" }, + { 0.9999999, "7-nines" }, + { 0.99999999, "8-nines" }, + }; + + for (auto p : percentiles) + { + string readPercentile = + fHasReads ? + Util::DoubleToStringHelper(readLatencyHistogram.GetPercentile(p.Percentile) / 1000) : + "N/A"; + + string writePercentile = + fHasWrites ? + Util::DoubleToStringHelper(writeLatencyHistogram.GetPercentile(p.Percentile) / 1000) : + "N/A"; + + _Print("%7s | %10s | %10s | %10.3lf\n", + p.Name.c_str(), + readPercentile.c_str(), + writePercentile.c_str(), + totalLatencyHistogram.GetPercentile(p.Percentile)/1000); + } + + string readMax = Util::DoubleToStringHelper(readLatencyHistogram.GetMax() / 1000); + string writeMax = Util::DoubleToStringHelper(writeLatencyHistogram.GetMax() / 1000); + + _Print(" max | %10s | %10s | %10.3lf\n", + fHasReads ? readMax.c_str() : "N/A", + fHasWrites ? writeMax.c_str() : "N/A", + totalLatencyHistogram.GetMax()/1000); +} + +int ResultParser::GetTotalScore() +{ + return _totalScore; +} + +double ResultParser::GetAverageLatency() +{ + return _averageLatency; +} + +string ResultParser::ParseResults(Profile& profile, const SystemInformation& system, vector vResults) +{ + // TODO: print text representation of system information (see xml parser) + UNREFERENCED_PARAMETER(system); + + _sResult.clear(); + + _PrintProfile(profile); + + for (size_t iResult = 0; iResult < vResults.size(); iResult++) + { + _Print("\n\nResults for timespan %d:\n", iResult + 1); + _Print("*******************************************************************************\n"); + + const Results& results = vResults[iResult]; + const TimeSpan& timeSpan = profile.GetTimeSpans()[iResult]; + + size_t ulProcCount = results.vSystemProcessorPerfInfo.size(); + double fTime = PerfTimer::PerfTimeToSeconds(results.ullTimeCount); //test duration + + char szFloatBuffer[1024]; + + // There either is a fixed number of threads for all files to share (GetThreadCount() > 0) or a number of threads per file. + // In the latter case vThreadResults.size() == number of threads per file * file count + size_t ulThreadCnt = (timeSpan.GetThreadCount() > 0) ? timeSpan.GetThreadCount() : results.vThreadResults.size(); + + if (fTime < 0.0000001) + { + _Print("The test was interrupted before the measurements began. No results are displayed.\n"); + } + else + { + // TODO: parameters.bCreateFile; + + _Print("\n"); + sprintf_s(szFloatBuffer, sizeof(szFloatBuffer), "actual test time:\t%.2lfs\n", fTime); + _Print("%s", szFloatBuffer); + _Print("thread count:\t\t%u\n", ulThreadCnt); + + _Print("proc count:\t\t%u\n", ulProcCount); + _PrintCpuUtilization(results); + + _Print("\nTotal IO\n"); + _PrintSection(_SectionEnum::TOTAL, timeSpan, results); + + _Print("\nRead IO\n"); + _PrintSection(_SectionEnum::READ, timeSpan, results); + + _Print("\nWrite IO\n"); + _PrintSection(_SectionEnum::WRITE, timeSpan, results); + + if (timeSpan.GetMeasureLatency()) + { + _Print("\n\n"); + _PrintLatencyPercentiles(results); + } + + //etw + if (results.fUseETW) + { + _DisplayETW(results.EtwMask, results.EtwEventCounters); + _DisplayETWSessionInfo(results.EtwSessionInfo); + } + } + } + + if (vResults.size() > 1) + { + _Print("\n\nTotals:\n"); + _Print("*******************************************************************************\n\n"); + _Print("type | bytes | I/Os | MB/s | I/O per s\n"); + _Print("-------------------------------------------------------------------------------\n"); + + + UINT64 cbTotalWritten = 0; + UINT64 cbTotalRead = 0; + UINT64 cTotalWriteIO = 0; + UINT64 cTotalReadIO = 0; + UINT64 cTotalTicks = 0; + for (auto pResults = vResults.begin(); pResults != vResults.end(); pResults++) + { + double time = PerfTimer::PerfTimeToSeconds(pResults->ullTimeCount); + if (time >= 0.0000001) // skip timespans that were interrupted + { + cTotalTicks += pResults->ullTimeCount; + auto vThreadResults = pResults->vThreadResults; + for (auto pThreadResults = vThreadResults.begin(); pThreadResults != vThreadResults.end(); pThreadResults++) + { + for (auto pTargetResults = pThreadResults->vTargetResults.begin(); pTargetResults != pThreadResults->vTargetResults.end(); pTargetResults++) + { + cbTotalRead += pTargetResults->ullReadBytesCount; + cbTotalWritten += pTargetResults->ullWriteBytesCount; + cTotalReadIO += pTargetResults->ullReadIOCount; + cTotalWriteIO += pTargetResults->ullWriteIOCount; + } + } + } + } + + double totalTime = PerfTimer::PerfTimeToSeconds(cTotalTicks); + + _Print("write | %15I64u | %12I64u | %10.2lf | %10.2lf\n", + cbTotalWritten, + cTotalWriteIO, + (double)cbTotalWritten / 1024 / 1024 / totalTime, + (double)cTotalWriteIO / totalTime); + + _Print("read | %15I64u | %12I64u | %10.2lf | %10.2lf\n", + cbTotalRead, + cTotalReadIO, + (double)cbTotalRead / 1024 / 1024 / totalTime, + (double)cTotalReadIO / totalTime); + _Print("-------------------------------------------------------------------------------\n"); + _Print("total | %15I64u | %12I64u | %10.2lf | %10.2lf\n\n", + cbTotalRead + cbTotalWritten, + cTotalReadIO + cTotalWriteIO, + (double)(cbTotalRead + cbTotalWritten) / 1024 / 1024 / totalTime, + (double)(cTotalReadIO + cTotalWriteIO) / totalTime); + + _Print("total test time:\t%.2lfs\n", totalTime); + } + + return _sResult; +} diff --git a/CristalDiskMark/source/diskspd2_0_15a/ResultParser/ResultParser.h b/CristalDiskMark/source/diskspd2_0_15a/ResultParser/ResultParser.h new file mode 100644 index 0000000..7a267d4 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/ResultParser/ResultParser.h @@ -0,0 +1,65 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#include "Common.h" + +namespace UnitTests +{ + class ResultParserUnitTests; +} + +class ResultParser : public IResultParser +{ +public: + string ParseResults(Profile& profile, const SystemInformation& system, vector vResults); + int GetTotalScore(); + double GetAverageLatency(); + +private: + void _DisplayFileSize(UINT64 fsize); + void _DisplayETWSessionInfo(struct ETWSessionInfo sessionInfo); + void _DisplayETW(struct ETWMask ETWMask, struct ETWEventCounters EtwEventCounters); + void _Print(const char *format, ...); + void _PrintProfile(const Profile& profile); + void _PrintCpuUtilization(const Results&); + enum class _SectionEnum {TOTAL, READ, WRITE}; + void _PrintSectionFieldNames(const TimeSpan& timeSpan); + void _PrintSectionBorderLine(const TimeSpan& timeSpan); + void _PrintSection(_SectionEnum, const TimeSpan&, const Results&); + void _PrintLatencyPercentiles(const Results&); + void _PrintTimeSpan(const TimeSpan &timeSpan); + void _PrintTarget(const Target &target, bool fUseThreadsPerFile, bool fCompletionRoutines); + + string _sResult; + int _totalScore; + double _averageLatency; + + friend class UnitTests::ResultParserUnitTests; +}; \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/XmlProfileParser/XmlProfileParser.cpp b/CristalDiskMark/source/diskspd2_0_15a/XmlProfileParser/XmlProfileParser.cpp new file mode 100644 index 0000000..920d6fb --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/XmlProfileParser/XmlProfileParser.cpp @@ -0,0 +1,885 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "XmlProfileParser.h" +#include +#include +#include + +// the vc com headers define a partial set of smartptr typedefs, which unfortunately +// aren't 1) complete for our use case and 2) vary between revs of the headers. +// this define disables the automatic definitions, letting us do them ourselves. +#define _COM_NO_STANDARD_GUIDS_ +#include + +_COM_SMARTPTR_TYPEDEF(IXMLDOMDocument2, __uuidof(IXMLDOMDocument2)); +_COM_SMARTPTR_TYPEDEF(IXMLDOMNode, __uuidof(IXMLDOMNodeList)); +_COM_SMARTPTR_TYPEDEF(IXMLDOMNodeList, __uuidof(IXMLDOMNodeList)); + +bool XmlProfileParser::ParseFile(const char *pszPath, Profile *pProfile) +{ + assert(pszPath != nullptr); + assert(pProfile != nullptr); + + bool fComInitialized = false; + HRESULT hr = CoInitialize(nullptr); + if (SUCCEEDED(hr)) + { + fComInitialized = true; + IXMLDOMDocument2Ptr spXmlDoc; + hr = CoCreateInstance(__uuidof(DOMDocument60), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spXmlDoc)); + if (SUCCEEDED(hr)) + { + hr = spXmlDoc->put_async(VARIANT_FALSE); + if (SUCCEEDED(hr)) + { + VARIANT_BOOL fvIsOk; + _variant_t vPath(pszPath); + hr = spXmlDoc->load(vPath, &fvIsOk); + if (SUCCEEDED(hr)) + { + bool fVerbose; + hr = _GetVerbose(spXmlDoc, &fVerbose); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetVerbose(fVerbose); + } + + if (SUCCEEDED(hr)) + { + DWORD dwProgress; + hr = _GetProgress(spXmlDoc, &dwProgress); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetProgress(dwProgress); + } + } + + if (SUCCEEDED(hr)) + { + string sResultFormat; + hr = _GetString(spXmlDoc, "//Profile/ResultFormat", &sResultFormat); + if (SUCCEEDED(hr) && (hr != S_FALSE) && sResultFormat == "xml") + { + pProfile->SetResultsFormat(ResultsFormat::Xml); + } + } + + if (SUCCEEDED(hr)) + { + string sCreateFiles; + hr = _GetString(spXmlDoc, "//Profile/PrecreateFiles", &sCreateFiles); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + if (sCreateFiles == "UseMaxSize") + { + pProfile->SetPrecreateFiles(PrecreateFiles::UseMaxSize); + } + else if (sCreateFiles == "CreateOnlyFilesWithConstantSizes") + { + pProfile->SetPrecreateFiles(PrecreateFiles::OnlyFilesWithConstantSizes); + } + else if (sCreateFiles == "CreateOnlyFilesWithConstantOrZeroSizes") + { + pProfile->SetPrecreateFiles(PrecreateFiles::OnlyFilesWithConstantOrZeroSizes); + } + else + { + hr = E_INVALIDARG; + } + } + } + + if (SUCCEEDED(hr)) + { + hr = _ParseEtw(spXmlDoc, pProfile); + } + + if (SUCCEEDED(hr)) + { + hr = _ParseTimeSpans(spXmlDoc, pProfile); + } + } + } + } + } + if (fComInitialized) + { + CoUninitialize(); + } + return SUCCEEDED(hr); +} + +HRESULT XmlProfileParser::_ParseEtw(IXMLDOMDocument2 &XmlDoc, Profile *pProfile) +{ + bool fEtwProcess; + HRESULT hr = _GetBool(XmlDoc, "//Profile/ETW/Process", &fEtwProcess); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwProcess(fEtwProcess); + } + + if (SUCCEEDED(hr)) + { + bool fEtwThread; + hr = _GetBool(XmlDoc, "//Profile/ETW/Thread", &fEtwThread); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwThread(fEtwThread); + } + } + + if (SUCCEEDED(hr)) + { + bool fEtwImageLoad; + hr = _GetBool(XmlDoc, "//Profile/ETW/ImageLoad", &fEtwImageLoad); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwImageLoad(fEtwImageLoad); + } + } + + if (SUCCEEDED(hr)) + { + bool fEtwDiskIO; + hr = _GetBool(XmlDoc, "//Profile/ETW/DiskIO", &fEtwDiskIO); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwDiskIO(fEtwDiskIO); + } + } + + if (SUCCEEDED(hr)) + { + bool fEtwMemoryPageFaults; + hr = _GetBool(XmlDoc, "//Profile/ETW/MemoryPageFaults", &fEtwMemoryPageFaults); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwMemoryPageFaults(fEtwMemoryPageFaults); + } + } + + if (SUCCEEDED(hr)) + { + bool fEtwMemoryHardFaults; + hr = _GetBool(XmlDoc, "//Profile/ETW/MemoryHardFaults", &fEtwMemoryHardFaults); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwMemoryHardFaults(fEtwMemoryHardFaults); + } + } + + if (SUCCEEDED(hr)) + { + bool fEtwNetwork; + hr = _GetBool(XmlDoc, "//Profile/ETW/Network", &fEtwNetwork); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwNetwork(fEtwNetwork); + } + } + + if (SUCCEEDED(hr)) + { + bool fEtwRegistry; + hr = _GetBool(XmlDoc, "//Profile/ETW/Registry", &fEtwRegistry); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwRegistry(fEtwRegistry); + } + } + + if (SUCCEEDED(hr)) + { + bool fEtwUsePagedMemory; + hr = _GetBool(XmlDoc, "//Profile/ETW/UsePagedMemory", &fEtwUsePagedMemory); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwUsePagedMemory(fEtwUsePagedMemory); + } + } + + if (SUCCEEDED(hr)) + { + bool fEtwUsePerfTimer; + hr = _GetBool(XmlDoc, "//Profile/ETW/UsePerfTimer", &fEtwUsePerfTimer); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwUsePerfTimer(fEtwUsePerfTimer); + } + } + + if (SUCCEEDED(hr)) + { + bool fEtwUseSystemTimer; + hr = _GetBool(XmlDoc, "//Profile/ETW/UseSystemTimer", &fEtwUseSystemTimer); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwUseSystemTimer(fEtwUseSystemTimer); + } + } + + if (SUCCEEDED(hr)) + { + bool fEtwUseCyclesCounter; + hr = _GetBool(XmlDoc, "//Profile/ETW/UseCyclesCounter", &fEtwUseCyclesCounter); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetEtwEnabled(true); + pProfile->SetEtwUseCyclesCounter(fEtwUseCyclesCounter); + } + } + + return hr; +} + +HRESULT XmlProfileParser::_ParseTimeSpans(IXMLDOMDocument2 &XmlDoc, Profile *pProfile) +{ + IXMLDOMNodeListPtr spNodeList; + _variant_t query("//Profile/TimeSpans/TimeSpan"); + HRESULT hr = XmlDoc.selectNodes(query.bstrVal, &spNodeList); + if (SUCCEEDED(hr)) + { + long cNodes; + hr = spNodeList->get_length(&cNodes); + if (SUCCEEDED(hr)) + { + for (int i = 0; i < cNodes; i++) + { + IXMLDOMNodePtr spNode; + hr = spNodeList->get_item(i, &spNode); + if (SUCCEEDED(hr)) + { + TimeSpan timeSpan; + hr = _ParseTimeSpan(spNode, &timeSpan); + if (SUCCEEDED(hr)) + { + pProfile->AddTimeSpan(timeSpan); + } + } + } + } + } + + return hr; +} + +HRESULT XmlProfileParser::_ParseTimeSpan(IXMLDOMNode &XmlNode, TimeSpan *pTimeSpan) +{ + UINT32 ulDuration; + HRESULT hr = _GetUINT32(XmlNode, "Duration", &ulDuration); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetDuration(ulDuration); + } + + if (SUCCEEDED(hr)) + { + UINT32 ulWarmup; + hr = _GetUINT32(XmlNode, "Warmup", &ulWarmup); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetWarmup(ulWarmup); + } + } + + if (SUCCEEDED(hr)) + { + UINT32 ulCooldown; + hr = _GetUINT32(XmlNode, "Cooldown", &ulCooldown); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetCooldown(ulCooldown); + } + } + + if (SUCCEEDED(hr)) + { + UINT32 ulRandSeed; + hr = _GetUINT32(XmlNode, "RandSeed", &ulRandSeed); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetRandSeed(ulRandSeed); + } + } + + if (SUCCEEDED(hr)) + { + UINT32 ulThreadCount; + hr = _GetUINT32(XmlNode, "ThreadCount", &ulThreadCount); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetThreadCount(ulThreadCount); + } + } + + if (SUCCEEDED(hr)) + { + bool fGroupAffinity; + hr = _GetBool(XmlNode, "GroupAffinity", &fGroupAffinity); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetGroupAffinity(fGroupAffinity); + } + } + + if (SUCCEEDED(hr)) + { + bool fDisableAffinity; + hr = _GetBool(XmlNode, "DisableAffinity", &fDisableAffinity); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetDisableAffinity(fDisableAffinity); + } + } + + if (SUCCEEDED(hr)) + { + bool fCompletionRoutines; + hr = _GetBool(XmlNode, "CompletionRoutines", &fCompletionRoutines); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetCompletionRoutines(fCompletionRoutines); + } + } + + if (SUCCEEDED(hr)) + { + bool fMeasureLatency; + hr = _GetBool(XmlNode, "MeasureLatency", &fMeasureLatency); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetMeasureLatency(fMeasureLatency); + } + } + + if (SUCCEEDED(hr)) + { + bool fCalculateIopsStdDev; + hr = _GetBool(XmlNode, "CalculateIopsStdDev", &fCalculateIopsStdDev); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetCalculateIopsStdDev(fCalculateIopsStdDev); + } + } + + if (SUCCEEDED(hr)) + { + UINT32 ulIoBucketDuration; + hr = _GetUINT32(XmlNode, "IoBucketDuration", &ulIoBucketDuration); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTimeSpan->SetIoBucketDurationInMilliseconds(ulIoBucketDuration); + } + } + + if (SUCCEEDED(hr)) + { + hr = _ParseAffinityAssignment(XmlNode, pTimeSpan); + } + + if (SUCCEEDED(hr)) + { + hr = _ParseTargets(XmlNode, pTimeSpan); + } + return hr; +} + +HRESULT XmlProfileParser::_ParseTargets(IXMLDOMNode &XmlNode, TimeSpan *pTimeSpan) +{ + _variant_t query("Targets/Target"); + IXMLDOMNodeListPtr spNodeList; + HRESULT hr = XmlNode.selectNodes(query.bstrVal, &spNodeList); + if (SUCCEEDED(hr)) + { + long cNodes; + hr = spNodeList->get_length(&cNodes); + if (SUCCEEDED(hr)) + { + for (int i = 0; i < cNodes; i++) + { + IXMLDOMNodePtr spNode; + hr = spNodeList->get_item(i, &spNode); + if (SUCCEEDED(hr)) + { + Target target; + _ParseTarget(spNode, &target); + pTimeSpan->AddTarget(target); + } + } + } + } + return hr; +} + +HRESULT XmlProfileParser::_ParseRandomDataSource(IXMLDOMNode &XmlNode, Target *pTarget) +{ + IXMLDOMNodeListPtr spNodeList; + _variant_t query("RandomDataSource"); + HRESULT hr = XmlNode.selectNodes(query.bstrVal, &spNodeList); + if (SUCCEEDED(hr)) + { + long cNodes; + hr = spNodeList->get_length(&cNodes); + if (SUCCEEDED(hr) && (cNodes == 1)) + { + IXMLDOMNodePtr spNode; + hr = spNodeList->get_item(0, &spNode); + if (SUCCEEDED(hr)) + { + UINT64 cb; + hr = _GetUINT64(spNode, "SizeInBytes", &cb); + if (SUCCEEDED(hr) && (S_FALSE != hr)) + { + pTarget->SetRandomDataWriteBufferSize(cb); + + string sPath; + hr = _GetString(spNode, "FilePath", &sPath); + if (SUCCEEDED(hr) && (S_FALSE != hr)) + { + pTarget->SetRandomDataWriteBufferSourcePath(sPath); + } + } + } + } + } + return hr; +} + +HRESULT XmlProfileParser::_ParseWriteBufferContent(IXMLDOMNode &XmlNode, Target *pTarget) +{ + IXMLDOMNodeListPtr spNodeList; + _variant_t query("WriteBufferContent"); + HRESULT hr = XmlNode.selectNodes(query.bstrVal, &spNodeList); + if (SUCCEEDED(hr)) + { + long cNodes; + hr = spNodeList->get_length(&cNodes); + if (SUCCEEDED(hr) && (cNodes == 1)) + { + IXMLDOMNodePtr spNode; + hr = spNodeList->get_item(0, &spNode); + if (SUCCEEDED(hr)) + { + string sPattern; + hr = _GetString(spNode, "Pattern", &sPattern); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + if (sPattern == "sequential") + { + // that's the default option - do nothing + } + else if (sPattern == "zero") + { + pTarget->SetZeroWriteBuffers(true); + } + else if (sPattern == "random") + { + hr = _ParseRandomDataSource(spNode, pTarget); + } + else + { + hr = E_INVALIDARG; + } + } + } + } + } + return hr; +} + +HRESULT XmlProfileParser::_ParseTarget(IXMLDOMNode &XmlNode, Target *pTarget) +{ + string sPath; + HRESULT hr = _GetString(XmlNode, "Path", &sPath); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetPath(sPath); + } + + if (SUCCEEDED(hr)) + { + DWORD dwBlockSize; + hr = _GetDWORD(XmlNode, "BlockSize", &dwBlockSize); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetBlockSizeInBytes(dwBlockSize); + } + } + + if (SUCCEEDED(hr)) + { + UINT64 ullStrideSize; + hr = _GetUINT64(XmlNode, "StrideSize", &ullStrideSize); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetBlockAlignmentInBytes(ullStrideSize); + } + } + + if (SUCCEEDED(hr)) + { + bool fInterlockedSequential; + hr = _GetBool(XmlNode, "InterlockedSequential", &fInterlockedSequential); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetUseInterlockedSequential(fInterlockedSequential); + } + } + + if (SUCCEEDED(hr)) + { + UINT64 ullBaseFileOffset; + hr = _GetUINT64(XmlNode, "BaseFileOffset", &ullBaseFileOffset); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetBaseFileOffsetInBytes(ullBaseFileOffset); + } + } + + if (SUCCEEDED(hr)) + { + bool fSequentialScan; + hr = _GetBool(XmlNode, "SequentialScan", &fSequentialScan); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetSequentialScanHint(fSequentialScan); + } + } + + if (SUCCEEDED(hr)) + { + bool fRandomAccess; + hr = _GetBool(XmlNode, "RandomAccess", &fRandomAccess); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetRandomAccessHint(fRandomAccess); + } + } + + if (SUCCEEDED(hr)) + { + bool fUseLargePages; + hr = _GetBool(XmlNode, "UseLargePages", &fUseLargePages); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetUseLargePages(fUseLargePages); + } + } + + if (SUCCEEDED(hr)) + { + DWORD dwRequestCount; + hr = _GetDWORD(XmlNode, "RequestCount", &dwRequestCount); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetRequestCount(dwRequestCount); + } + } + + if (SUCCEEDED(hr)) + { + UINT64 ullRandom; + hr = _GetUINT64(XmlNode, "Random", &ullRandom); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetUseRandomAccessPattern(true); + pTarget->SetBlockAlignmentInBytes(ullRandom); + } + } + + if (SUCCEEDED(hr)) + { + bool fDisableOSCache; + hr = _GetBool(XmlNode, "DisableOSCache", &fDisableOSCache); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetDisableOSCache(fDisableOSCache); + } + } + + if (SUCCEEDED(hr)) + { + bool fDisableAllCache; + hr = _GetBool(XmlNode, "DisableAllCache", &fDisableAllCache); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetDisableAllCache(fDisableAllCache); + } + } + + if (SUCCEEDED(hr)) + { + hr = _ParseWriteBufferContent(XmlNode, pTarget); + } + + if (SUCCEEDED(hr)) + { + DWORD dwBurstSize; + hr = _GetDWORD(XmlNode, "BurstSize", &dwBurstSize); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetBurstSize(dwBurstSize); + pTarget->SetUseBurstSize(true); + } + } + + if (SUCCEEDED(hr)) + { + DWORD dwThinkTime; + hr = _GetDWORD(XmlNode, "ThinkTime", &dwThinkTime); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetThinkTime(dwThinkTime); + pTarget->SetEnableThinkTime(true); + } + } + + if (SUCCEEDED(hr)) + { + DWORD dwThroughput; + hr = _GetDWORD(XmlNode, "Throughput", &dwThroughput); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetThroughput(dwThroughput); + } + } + + if (SUCCEEDED(hr)) + { + DWORD dwThreadsPerFile; + hr = _GetDWORD(XmlNode, "ThreadsPerFile", &dwThreadsPerFile); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetThreadsPerFile(dwThreadsPerFile); + } + } + + if (SUCCEEDED(hr)) + { + UINT64 ullFileSize; + hr = _GetUINT64(XmlNode, "FileSize", &ullFileSize); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetFileSize(ullFileSize); + pTarget->SetCreateFile(true); + } + } + + if (SUCCEEDED(hr)) + { + UINT64 ullMaxFileSize; + hr = _GetUINT64(XmlNode, "MaxFileSize", &ullMaxFileSize); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetMaxFileSize(ullMaxFileSize); + } + } + + if (SUCCEEDED(hr)) + { + UINT32 ulWriteRatio; + hr = _GetUINT32(XmlNode, "WriteRatio", &ulWriteRatio); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetWriteRatio(ulWriteRatio); + } + } + + if (SUCCEEDED(hr)) + { + bool fParallelAsyncIO; + hr = _GetBool(XmlNode, "ParallelAsyncIO", &fParallelAsyncIO); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetUseParallelAsyncIO(fParallelAsyncIO); + } + } + + if (SUCCEEDED(hr)) + { + UINT64 ullThreadStride; + hr = _GetUINT64(XmlNode, "ThreadStride", &ullThreadStride); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetThreadStrideInBytes(ullThreadStride); + } + } + + if (SUCCEEDED(hr)) + { + UINT32 ulIOPriority; + hr = _GetUINT32(XmlNode, "IOPriority", &ulIOPriority); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + PRIORITY_HINT hint[] = { IoPriorityHintVeryLow, IoPriorityHintLow, IoPriorityHintNormal }; + pTarget->SetIOPriorityHint(hint[ulIOPriority - 1]); + } + } + return hr; +} + +HRESULT XmlProfileParser::_ParseAffinityAssignment(IXMLDOMNode &XmlNode, TimeSpan *pTimeSpan) +{ + IXMLDOMNodeListPtr spNodeList; + _variant_t query("Affinity/AffinityAssignment"); + HRESULT hr = XmlNode.selectNodes(query.bstrVal, &spNodeList); + if (SUCCEEDED(hr)) + { + long cNodes; + hr = spNodeList->get_length(&cNodes); + if (SUCCEEDED(hr)) + { + for (int i = 0; i < cNodes; i++) + { + IXMLDOMNodePtr spNode; + hr = spNodeList->get_item(i, &spNode); + if (SUCCEEDED(hr)) + { + BSTR bstrText; + hr = spNode->get_text(&bstrText); + if (SUCCEEDED(hr)) + { + pTimeSpan->AddAffinityAssignment(_wtoi((wchar_t *)bstrText)); // TODO: change to unsigned + SysFreeString(bstrText); + } + } + } + } + } + return hr; +} + +HRESULT XmlProfileParser::_GetUINT32(IXMLDOMNode &XmlNode, const char *pszQuery, UINT32 *pulValue) const +{ + IXMLDOMNodePtr spNode; + _variant_t query(pszQuery); + HRESULT hr = XmlNode.selectSingleNode(query.bstrVal, &spNode); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + BSTR bstrText; + hr = spNode->get_text(&bstrText); + if (SUCCEEDED(hr)) + { + *pulValue = _wtoi((wchar_t *)bstrText); // TODO: make sure it works on large unsigned ints + SysFreeString(bstrText); + } + } + return hr; +} + +HRESULT XmlProfileParser::_GetString(IXMLDOMNode &XmlNode, const char *pszQuery, string *psValue) const +{ + IXMLDOMNodePtr spNode; + _variant_t query(pszQuery); + HRESULT hr = XmlNode.selectSingleNode(query.bstrVal, &spNode); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + BSTR bstrText; + hr = spNode->get_text(&bstrText); + if (SUCCEEDED(hr)) + { + // TODO: use wstring? + char path[MAX_PATH] = {}; + WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, (wchar_t *)bstrText, static_cast(wcslen((wchar_t *)bstrText)), path, sizeof(path)-1, 0 /*lpDefaultChar*/, 0 /*lpUsedDefaultChar*/); + *psValue = string(path); + } + SysFreeString(bstrText); + } + return hr; +} + +HRESULT XmlProfileParser::_GetUINT64(IXMLDOMNode &XmlNode, const char *pszQuery, UINT64 *pullValue) const +{ + IXMLDOMNodePtr spNode; + _variant_t query(pszQuery); + HRESULT hr = XmlNode.selectSingleNode(query.bstrVal, &spNode); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + BSTR bstrText; + hr = spNode->get_text(&bstrText); + if (SUCCEEDED(hr)) + { + *pullValue = _wtoi64((wchar_t *)bstrText); // TODO: make sure it works on large unsigned ints + } + SysFreeString(bstrText); + } + return hr; +} + +HRESULT XmlProfileParser::_GetDWORD(IXMLDOMNode &XmlNode, const char *pszQuery, DWORD *pdwValue) const +{ + UINT32 value = 0; + HRESULT hr = _GetUINT32(XmlNode, pszQuery, &value); + if (SUCCEEDED(hr)) + { + *pdwValue = value; + } + return hr; +} + +HRESULT XmlProfileParser::_GetBool(IXMLDOMNode &XmlNode, const char *pszQuery, bool *pfValue) const +{ + HRESULT hr = S_OK; + IXMLDOMNodePtr spNode; + _variant_t query(pszQuery); + hr = XmlNode.selectSingleNode(query.bstrVal, &spNode); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + BSTR bstrText; + hr = spNode->get_text(&bstrText); + if (SUCCEEDED(hr)) + { + *pfValue = (_wcsicmp(L"true", (wchar_t *)bstrText) == 0); + SysFreeString(bstrText); + } + } + return hr; +} + +HRESULT XmlProfileParser::_GetVerbose(IXMLDOMDocument2 &pXmlDoc, bool *pfVerbose) +{ + return _GetBool(pXmlDoc, "//Profile/Verbose", pfVerbose); +} + +HRESULT XmlProfileParser::_GetProgress(IXMLDOMDocument2 &pXmlDoc, DWORD *pdwProgress) +{ + return _GetDWORD(pXmlDoc, "//Profile/Progress", pdwProgress); +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/XmlProfileParser/XmlProfileParser.h b/CristalDiskMark/source/diskspd2_0_15a/XmlProfileParser/XmlProfileParser.h new file mode 100644 index 0000000..a378b73 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/XmlProfileParser/XmlProfileParser.h @@ -0,0 +1,57 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#include +#include "Common.h" + +class XmlProfileParser +{ +public: + bool ParseFile(const char *pszPath, Profile *pProfile); + +private: + HRESULT _ParseEtw(IXMLDOMDocument2 &XmlDoc, Profile *pProfile); + HRESULT _ParseTimeSpans(IXMLDOMDocument2 &XmlDoc, Profile *pProfile); + HRESULT _ParseTimeSpan(IXMLDOMNode &XmlNode, TimeSpan *pTimeSpan); + HRESULT _ParseTargets(IXMLDOMNode &XmlNode, TimeSpan *pTimeSpan); + HRESULT _ParseRandomDataSource(IXMLDOMNode &XmlNode, Target *pTarget); + HRESULT _ParseWriteBufferContent(IXMLDOMNode &XmlNode, Target *pTarget); + HRESULT _ParseTarget(IXMLDOMNode &XmlNode, Target *pTarget); + HRESULT _ParseAffinityAssignment(IXMLDOMNode &XmlNode, TimeSpan *pTimeSpan); + + HRESULT _GetString(IXMLDOMNode &XmlNode, const char *pszQuery, string *psValue) const; + HRESULT _GetUINT32(IXMLDOMNode &XmlNode, const char *pszQuery, UINT32 *pulValue) const; + HRESULT _GetUINT64(IXMLDOMNode &XmlNode, const char *pszQuery, UINT64 *pullValue) const; + HRESULT _GetDWORD(IXMLDOMNode &XmlNode, const char *pszQuery, DWORD *pdwValue) const; + HRESULT _GetBool(IXMLDOMNode &XmlNode, const char *pszQuery, bool *pfValue) const; + + HRESULT _GetVerbose(IXMLDOMDocument2 &XmlDoc, bool *pfVerbose); + HRESULT _GetProgress(IXMLDOMDocument2 &XmlDoc, DWORD *pdwProgress); +}; \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/XmlProfileParser/diskspd.h b/CristalDiskMark/source/diskspd2_0_15a/XmlProfileParser/diskspd.h new file mode 100644 index 0000000..befd642 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/XmlProfileParser/diskspd.h @@ -0,0 +1,273 @@ +#pragma once + +#using +#using +#using +#using + +using namespace System::Security::Permissions; +[assembly:SecurityPermissionAttribute(SecurityAction::RequestMinimum, SkipVerification=false)]; +// +// このソース コードは xsd によって自動生成されました。Version=4.0.30319.33440 です。 +// +using namespace System; +ref class NewDataSet; + + +/// +///Represents a strongly typed in-memory cache of data. +/// +[System::Serializable, +System::ComponentModel::DesignerCategoryAttribute(L"code"), +System::ComponentModel::ToolboxItem(true), +System::Xml::Serialization::XmlSchemaProviderAttribute(L"GetTypedDataSetSchema"), +System::Xml::Serialization::XmlRootAttribute(L"NewDataSet"), +System::ComponentModel::Design::HelpKeywordAttribute(L"vs.data.DataSet")] +public ref class NewDataSet : public ::System::Data::DataSet { + + private: ::System::Data::SchemaSerializationMode _schemaSerializationMode; + + public: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + NewDataSet(); + protected: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + NewDataSet(::System::Runtime::Serialization::SerializationInfo^ info, ::System::Runtime::Serialization::StreamingContext context); + public: [System::Diagnostics::DebuggerNonUserCodeAttribute, + System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0"), + System::ComponentModel::BrowsableAttribute(true), + System::ComponentModel::DesignerSerializationVisibilityAttribute(::System::ComponentModel::DesignerSerializationVisibility::Visible)] + virtual property ::System::Data::SchemaSerializationMode SchemaSerializationMode { + ::System::Data::SchemaSerializationMode get() override; + System::Void set(::System::Data::SchemaSerializationMode value) override; + } + + public: [System::Diagnostics::DebuggerNonUserCodeAttribute, + System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0"), + System::ComponentModel::DesignerSerializationVisibilityAttribute(::System::ComponentModel::DesignerSerializationVisibility::Hidden)] + property ::System::Data::DataTableCollection^ Tables { + ::System::Data::DataTableCollection^ get() new; + } + + public: [System::Diagnostics::DebuggerNonUserCodeAttribute, + System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0"), + System::ComponentModel::DesignerSerializationVisibilityAttribute(::System::ComponentModel::DesignerSerializationVisibility::Hidden)] + property ::System::Data::DataRelationCollection^ Relations { + ::System::Data::DataRelationCollection^ get() new; + } + + protected: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + virtual ::System::Void InitializeDerivedDataSet() override; + + public: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + virtual ::System::Data::DataSet^ Clone() override; + + protected: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + virtual ::System::Boolean ShouldSerializeTables() override; + + protected: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + virtual ::System::Boolean ShouldSerializeRelations() override; + + protected: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + virtual ::System::Void ReadXmlSerializable(::System::Xml::XmlReader^ reader) override; + + protected: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + virtual ::System::Xml::Schema::XmlSchema^ GetSchemaSerializable() override; + + internal: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + ::System::Void InitVars(); + + internal: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + ::System::Void InitVars(::System::Boolean initTable); + + private: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + ::System::Void InitClass(); + + private: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + ::System::Void SchemaChanged(::System::Object^ sender, ::System::ComponentModel::CollectionChangeEventArgs^ e); + + public: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + static ::System::Xml::Schema::XmlSchemaComplexType^ GetTypedDataSetSchema(::System::Xml::Schema::XmlSchemaSet^ xs); +}; + + +inline NewDataSet::NewDataSet() { + this->BeginInit(); + this->InitClass(); + ::System::ComponentModel::CollectionChangeEventHandler^ schemaChangedHandler = gcnew ::System::ComponentModel::CollectionChangeEventHandler(this, &NewDataSet::SchemaChanged); + __super::Tables->CollectionChanged += schemaChangedHandler; + __super::Relations->CollectionChanged += schemaChangedHandler; + this->EndInit(); +} + +inline NewDataSet::NewDataSet(::System::Runtime::Serialization::SerializationInfo^ info, ::System::Runtime::Serialization::StreamingContext context) : + ::System::Data::DataSet(info, context, false) { + if (this->IsBinarySerialized(info, context) == true) { + this->InitVars(false); + ::System::ComponentModel::CollectionChangeEventHandler^ schemaChangedHandler1 = gcnew ::System::ComponentModel::CollectionChangeEventHandler(this, &NewDataSet::SchemaChanged); + this->Tables->CollectionChanged += schemaChangedHandler1; + this->Relations->CollectionChanged += schemaChangedHandler1; + return; + } + ::System::String^ strSchema = (cli::safe_cast<::System::String^ >(info->GetValue(L"XmlSchema", ::System::String::typeid))); + if (this->DetermineSchemaSerializationMode(info, context) == ::System::Data::SchemaSerializationMode::IncludeSchema) { + ::System::Data::DataSet^ ds = (gcnew ::System::Data::DataSet()); + ds->ReadXmlSchema((gcnew ::System::Xml::XmlTextReader((gcnew ::System::IO::StringReader(strSchema))))); + this->DataSetName = ds->DataSetName; + this->Prefix = ds->Prefix; + this->Namespace = ds->Namespace; + this->Locale = ds->Locale; + this->CaseSensitive = ds->CaseSensitive; + this->EnforceConstraints = ds->EnforceConstraints; + this->Merge(ds, false, ::System::Data::MissingSchemaAction::Add); + this->InitVars(); + } + else { + this->ReadXmlSchema((gcnew ::System::Xml::XmlTextReader((gcnew ::System::IO::StringReader(strSchema))))); + } + this->GetSerializationData(info, context); + ::System::ComponentModel::CollectionChangeEventHandler^ schemaChangedHandler = gcnew ::System::ComponentModel::CollectionChangeEventHandler(this, &NewDataSet::SchemaChanged); + __super::Tables->CollectionChanged += schemaChangedHandler; + this->Relations->CollectionChanged += schemaChangedHandler; +} + +inline ::System::Data::SchemaSerializationMode NewDataSet::SchemaSerializationMode::get() { + return this->_schemaSerializationMode; +} +inline System::Void NewDataSet::SchemaSerializationMode::set(::System::Data::SchemaSerializationMode value) { + this->_schemaSerializationMode = __identifier(value); +} + +inline ::System::Data::DataTableCollection^ NewDataSet::Tables::get() { + return __super::Tables; +} + +inline ::System::Data::DataRelationCollection^ NewDataSet::Relations::get() { + return __super::Relations; +} + +inline ::System::Void NewDataSet::InitializeDerivedDataSet() { + this->BeginInit(); + this->InitClass(); + this->EndInit(); +} + +inline ::System::Data::DataSet^ NewDataSet::Clone() { + NewDataSet^ cln = (cli::safe_cast(__super::Clone())); + cln->InitVars(); + cln->SchemaSerializationMode = this->SchemaSerializationMode; + return cln; +} + +inline ::System::Boolean NewDataSet::ShouldSerializeTables() { + return false; +} + +inline ::System::Boolean NewDataSet::ShouldSerializeRelations() { + return false; +} + +inline ::System::Void NewDataSet::ReadXmlSerializable(::System::Xml::XmlReader^ reader) { + if (this->DetermineSchemaSerializationMode(reader) == ::System::Data::SchemaSerializationMode::IncludeSchema) { + this->Reset(); + ::System::Data::DataSet^ ds = (gcnew ::System::Data::DataSet()); + ds->ReadXml(reader); + this->DataSetName = ds->DataSetName; + this->Prefix = ds->Prefix; + this->Namespace = ds->Namespace; + this->Locale = ds->Locale; + this->CaseSensitive = ds->CaseSensitive; + this->EnforceConstraints = ds->EnforceConstraints; + this->Merge(ds, false, ::System::Data::MissingSchemaAction::Add); + this->InitVars(); + } + else { + this->ReadXml(reader); + this->InitVars(); + } +} + +inline ::System::Xml::Schema::XmlSchema^ NewDataSet::GetSchemaSerializable() { + ::System::IO::MemoryStream^ stream = (gcnew ::System::IO::MemoryStream()); + this->WriteXmlSchema((gcnew ::System::Xml::XmlTextWriter(stream, nullptr))); + stream->Position = 0; + return ::System::Xml::Schema::XmlSchema::Read((gcnew ::System::Xml::XmlTextReader(stream)), nullptr); +} + +inline ::System::Void NewDataSet::InitVars() { + this->InitVars(true); +} + +inline ::System::Void NewDataSet::InitVars(::System::Boolean initTable) { +} + +inline ::System::Void NewDataSet::InitClass() { + this->DataSetName = L"NewDataSet"; + this->Prefix = L""; + this->Namespace = L"http://microsoft.com/diskspd/DiskSpdConfig.xsd"; + this->Locale = (gcnew ::System::Globalization::CultureInfo(L"")); + this->EnforceConstraints = true; + this->SchemaSerializationMode = ::System::Data::SchemaSerializationMode::IncludeSchema; +} + +inline ::System::Void NewDataSet::SchemaChanged(::System::Object^ sender, ::System::ComponentModel::CollectionChangeEventArgs^ e) { + if (e->Action == ::System::ComponentModel::CollectionChangeAction::Remove) { + this->InitVars(); + } +} + +inline ::System::Xml::Schema::XmlSchemaComplexType^ NewDataSet::GetTypedDataSetSchema(::System::Xml::Schema::XmlSchemaSet^ xs) { + NewDataSet^ ds = (gcnew NewDataSet()); + ::System::Xml::Schema::XmlSchemaComplexType^ type = (gcnew ::System::Xml::Schema::XmlSchemaComplexType()); + ::System::Xml::Schema::XmlSchemaSequence^ sequence = (gcnew ::System::Xml::Schema::XmlSchemaSequence()); + ::System::Xml::Schema::XmlSchemaAny^ any = (gcnew ::System::Xml::Schema::XmlSchemaAny()); + any->Namespace = ds->Namespace; + sequence->Items->Add(any); + type->Particle = sequence; + ::System::Xml::Schema::XmlSchema^ dsSchema = ds->GetSchemaSerializable(); + if (xs->Contains(dsSchema->TargetNamespace)) { + ::System::IO::MemoryStream^ s1 = (gcnew ::System::IO::MemoryStream()); + ::System::IO::MemoryStream^ s2 = (gcnew ::System::IO::MemoryStream()); + try { + ::System::Xml::Schema::XmlSchema^ schema = nullptr; + dsSchema->Write(s1); + for ( ::System::Collections::IEnumerator^ schemas = xs->Schemas(dsSchema->TargetNamespace)->GetEnumerator(); schemas->MoveNext(); ) { + schema = (cli::safe_cast<::System::Xml::Schema::XmlSchema^ >(schemas->Current)); + s2->SetLength(0); + schema->Write(s2); + if (s1->Length == s2->Length) { + s1->Position = 0; + s2->Position = 0; + for ( ; ((s1->Position != s1->Length) + && (s1->ReadByte() == s2->ReadByte())); ) { + ; + } + if (s1->Position == s1->Length) { + return type; + } + } + } + } + finally { + if (s1 != nullptr) { + s1->Close(); + } + if (s2 != nullptr) { + s2->Close(); + } + } + } + xs->Add(dsSchema); + return type; +} diff --git a/CristalDiskMark/source/diskspd2_0_15a/XmlProfileParser/diskspd.xsd b/CristalDiskMark/source/diskspd2_0_15a/XmlProfileParser/diskspd.xsd new file mode 100644 index 0000000..945d578 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/XmlProfileParser/diskspd.xsd @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CristalDiskMark/source/diskspd2_0_15a/XmlResultParser/exe/DiskSpd32.exe b/CristalDiskMark/source/diskspd2_0_15a/XmlResultParser/exe/DiskSpd32.exe new file mode 100644 index 0000000..e52a2ad Binary files /dev/null and b/CristalDiskMark/source/diskspd2_0_15a/XmlResultParser/exe/DiskSpd32.exe differ diff --git a/CristalDiskMark/source/diskspd2_0_15a/XmlResultParser/exe/DiskSpd64.exe b/CristalDiskMark/source/diskspd2_0_15a/XmlResultParser/exe/DiskSpd64.exe new file mode 100644 index 0000000..cd2239c Binary files /dev/null and b/CristalDiskMark/source/diskspd2_0_15a/XmlResultParser/exe/DiskSpd64.exe differ diff --git a/CristalDiskMark/source/diskspd2_0_15a/XmlResultParser/exe/DiskSpdA32.exe b/CristalDiskMark/source/diskspd2_0_15a/XmlResultParser/exe/DiskSpdA32.exe new file mode 100644 index 0000000..88905a3 Binary files /dev/null and b/CristalDiskMark/source/diskspd2_0_15a/XmlResultParser/exe/DiskSpdA32.exe differ diff --git a/CristalDiskMark/source/diskspd2_0_15a/XmlResultParser/exe/DiskSpdA64.exe b/CristalDiskMark/source/diskspd2_0_15a/XmlResultParser/exe/DiskSpdA64.exe new file mode 100644 index 0000000..7a6e95b Binary files /dev/null and b/CristalDiskMark/source/diskspd2_0_15a/XmlResultParser/exe/DiskSpdA64.exe differ diff --git a/CristalDiskMark/source/diskspd2_0_15a/XmlResultParser/xmlresultparser.cpp b/CristalDiskMark/source/diskspd2_0_15a/XmlResultParser/xmlresultparser.cpp new file mode 100644 index 0000000..4e2ff22 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/XmlResultParser/xmlresultparser.cpp @@ -0,0 +1,482 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "xmlresultparser.h" + +// TODO: refactor to a single function shared with the ResultParser +void XmlResultParser::_Print(const char *format, ...) +{ + assert(nullptr != format); + va_list listArg; + va_start(listArg, format); + char buffer[4096] = {}; + vsprintf_s(buffer, _countof(buffer), format, listArg); + va_end(listArg); + _sResult += buffer; +} + + +void XmlResultParser::_PrintTargetResults(const TargetResults& results) +{ + // TODO: results.readBucketizer; + // TODO: results.writeBucketizer; + + _Print("%s\n", results.sPath.c_str()); + _Print("%I64u\n", results.ullBytesCount); + _Print("%I64u\n", results.ullFileSize); + _Print("%I64u\n", results.ullIOCount); + _Print("%I64u\n", results.ullReadBytesCount); + _Print("%I64u\n", results.ullReadIOCount); + _Print("%I64u\n", results.ullWriteBytesCount); + _Print("%I64u\n", results.ullWriteIOCount); +} + +void XmlResultParser::_PrintTargetLatency(const TargetResults& results) +{ + if (results.readLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", results.readLatencyHistogram.GetAvg() / 1000); + _Print("%.3f\n", results.readLatencyHistogram.GetStandardDeviation() / 1000); + } + if (results.writeLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", results.writeLatencyHistogram.GetAvg() / 1000); + _Print("%.3f\n", results.writeLatencyHistogram.GetStandardDeviation() / 1000); + } + Histogram totalLatencyHistogram; + totalLatencyHistogram.Merge(results.readLatencyHistogram); + totalLatencyHistogram.Merge(results.writeLatencyHistogram); + if (totalLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", totalLatencyHistogram.GetAvg() / 1000); + _Print("%.3f\n", totalLatencyHistogram.GetStandardDeviation() / 1000); + } +} + +void XmlResultParser::_PrintTargetIops(const IoBucketizer& readBucketizer, const IoBucketizer& writeBucketizer, UINT32 bucketTimeInMs) +{ + _Print("\n"); + + IoBucketizer totalIoBucketizer; + totalIoBucketizer.Merge(readBucketizer); + totalIoBucketizer.Merge(writeBucketizer); + + if (readBucketizer.GetNumberOfValidBuckets() > 0) + { + _Print("%.3f\n", readBucketizer.GetStandardDeviation() / (bucketTimeInMs / 1000.0)); + } + if (writeBucketizer.GetNumberOfValidBuckets() > 0) + { + _Print("%.3f\n", writeBucketizer.GetStandardDeviation() / (bucketTimeInMs / 1000.0)); + } + if (totalIoBucketizer.GetNumberOfValidBuckets() > 0) + { + _Print("%.3f\n", totalIoBucketizer.GetStandardDeviation() / (bucketTimeInMs / 1000.0)); + } + _PrintIops(readBucketizer, writeBucketizer, bucketTimeInMs); + _Print("\n"); +} + +void XmlResultParser::_PrintETWSessionInfo(struct ETWSessionInfo sessionInfo) +{ + _Print("\n"); + _Print("%lu\n", sessionInfo.ulBufferSize); + _Print("%lu\n", sessionInfo.ulMinimumBuffers); + _Print("%lu\n", sessionInfo.ulMaximumBuffers); + _Print("%lu", sessionInfo.ulFreeBuffers); + _Print("%lu\n", sessionInfo.ulBuffersWritten); + _Print("%lu\n", sessionInfo.ulFlushTimer); + _Print("%d\n", sessionInfo.lAgeLimit); + + _Print("%lu\n", sessionInfo.ulNumberOfBuffers); + _Print("%15lu\n", sessionInfo.ulEventsLost); + _Print("%10lu\n", sessionInfo.ulLogBuffersLost); + _Print("%4lu\n", sessionInfo.ulRealTimeBuffersLost); + _Print("\n"); +} + +void XmlResultParser::_PrintETW(struct ETWMask ETWMask, struct ETWEventCounters EtwEventCounters) +{ + _Print("\n"); + if (ETWMask.bDiskIO) + { + _Print("\n"); + _Print("%I64u\n", EtwEventCounters.ullIORead); + _Print("%I64u\n", EtwEventCounters.ullIOWrite); + _Print("\n"); + } + if (ETWMask.bImageLoad) + { + _Print("%I64u\n", EtwEventCounters.ullImageLoad); + } + if (ETWMask.bMemoryPageFaults) + { + _Print("\n"); + _Print("%I64u\n", EtwEventCounters.ullMMCopyOnWrite); + _Print("%I64u\n", EtwEventCounters.ullMMDemandZeroFault); + _Print("%I64u\n", EtwEventCounters.ullMMGuardPageFault); + _Print("%I64u\n", EtwEventCounters.ullMMHardPageFault); + _Print("%I64u\n", EtwEventCounters.ullMMTransitionFault); + _Print("\n"); + } + if (ETWMask.bMemoryHardFaults && !ETWMask.bMemoryPageFaults) + { + _Print("%I64u\n", EtwEventCounters.ullMMHardPageFault); + } + if (ETWMask.bNetwork) + { + _Print("\n"); + _Print("%I64u\n", EtwEventCounters.ullNetAccept); + _Print("%I64u\n", EtwEventCounters.ullNetConnect); + _Print("%I64u\n", EtwEventCounters.ullNetDisconnect); + _Print("%I64u\n", EtwEventCounters.ullNetReconnect); + _Print("%I64u\n", EtwEventCounters.ullNetRetransmit); + _Print("%I64u\n", EtwEventCounters.ullNetTcpSend); + _Print("%I64u\n", EtwEventCounters.ullNetTcpReceive); + _Print("%I64u\n", EtwEventCounters.ullNetUdpSend); + _Print("%I64u\n", EtwEventCounters.ullNetUdpReceive); + _Print("\n"); + } + if (ETWMask.bProcess) + { + _Print("\n"); + _Print("%I64u\n", EtwEventCounters.ullProcessStart); + _Print("%I64u\n", EtwEventCounters.ullProcessEnd); + _Print("\n"); + } + if (ETWMask.bRegistry) + { + _Print("\n"); + _Print("%I64u\n", EtwEventCounters.ullRegCreate); + _Print("%I64u\n", EtwEventCounters.ullRegDelete); + _Print("%I64u\n", EtwEventCounters.ullRegDeleteValue); + _Print("%I64u\n", EtwEventCounters.ullRegEnumerateKey); + _Print("%I64u\n", EtwEventCounters.ullRegEnumerateValueKey); + _Print("%I64u\n", EtwEventCounters.ullRegFlush); + _Print("%I64u\n", EtwEventCounters.ullRegKcbDmp); + _Print("%I64u\n", EtwEventCounters.ullRegOpen); + _Print("%I64u\n", EtwEventCounters.ullRegQuery); + _Print("%I64u\n", EtwEventCounters.ullRegQueryMultipleValue); + _Print("%I64u\n", EtwEventCounters.ullRegQueryValue); + _Print("%I64u\n", EtwEventCounters.ullRegSetInformation); + _Print("%I64u\n", EtwEventCounters.ullRegSetValue); + _Print("\n"); + } + if (ETWMask.bThread) + { + _Print("\n"); + _Print("%I64u\n", EtwEventCounters.ullThreadStart); + _Print("%I64u\n", EtwEventCounters.ullThreadEnd); + _Print("\n"); + } + _Print("\n"); +} + +void XmlResultParser::_PrintCpuUtilization(const Results& results) +{ + size_t ulProcCount = results.vSystemProcessorPerfInfo.size(); + double fTime = PerfTimer::PerfTimeToSeconds(results.ullTimeCount); + + _Print("\n"); + + double busyTime = 0; + double totalIdleTime = 0; + double totalUserTime = 0; + double totalKrnlTime = 0; + + for (unsigned int x = 0; x\n"); + _Print("%d\n", x); + _Print("%.2f\n", thisTime); + _Print("%.2f\n", userTime); + _Print("%.2f\n", krnlTime - idleTime); + _Print("%.2f\n", idleTime); + _Print("\n"); + + busyTime += thisTime; + totalIdleTime += idleTime; + totalUserTime += userTime; + totalKrnlTime += krnlTime; + } + _Print("\n"); + _Print("%.2f\n", busyTime / ulProcCount); + _Print("%.2f\n", totalUserTime / ulProcCount); + _Print("%.2f\n", (totalKrnlTime - totalIdleTime) / ulProcCount); + _Print("%.2f\n", totalIdleTime / ulProcCount); + _Print("\n"); + + _Print("\n"); +} + +// emit the iops time series (this obviates needing perfmon counters, in common cases, and provides file level data) +void XmlResultParser::_PrintIops(const IoBucketizer& readBucketizer, const IoBucketizer& writeBucketizer, UINT32 bucketTimeInMs) +{ + bool done = false; + for (size_t i = 0; !done; i++) + { + done = true; + + double r = 0.0; + double w = 0.0; + + if (readBucketizer.GetNumberOfValidBuckets() > i) + { + r = readBucketizer.GetIoBucket(i) / (bucketTimeInMs / 1000.0); + done = false; + } + if (writeBucketizer.GetNumberOfValidBuckets() > i) + { + w = writeBucketizer.GetIoBucket(i) / (bucketTimeInMs / 1000.0); + done = false; + } + if (!done) + { + _Print("\n", bucketTimeInMs*(i + 1), r, w, r + w); + } + } +} + +void XmlResultParser::_PrintOverallIops(const Results& results, UINT32 bucketTimeInMs) +{ + IoBucketizer readBucketizer; + IoBucketizer writeBucketizer; + + for (const auto& thread : results.vThreadResults) + { + for (const auto& target : thread.vTargetResults) + { + readBucketizer.Merge(target.readBucketizer); + writeBucketizer.Merge(target.writeBucketizer); + } + } + + _PrintTargetIops(readBucketizer, writeBucketizer, bucketTimeInMs); +} + +void XmlResultParser::_PrintLatencyPercentiles(const Results& results) +{ + Histogram readLatencyHistogram; + Histogram writeLatencyHistogram; + Histogram totalLatencyHistogram; + + for (const auto& thread : results.vThreadResults) + { + for (const auto& target : thread.vTargetResults) + { + readLatencyHistogram.Merge(target.readLatencyHistogram); + + writeLatencyHistogram.Merge(target.writeLatencyHistogram); + + totalLatencyHistogram.Merge(target.writeLatencyHistogram); + totalLatencyHistogram.Merge(target.readLatencyHistogram); + } + } + + _Print("\n"); + if (readLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", readLatencyHistogram.GetAvg() / 1000); + _Print("%.3f\n", readLatencyHistogram.GetStandardDeviation() / 1000); + } + if (writeLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", writeLatencyHistogram.GetAvg() / 1000); + _Print("%.3f\n", writeLatencyHistogram.GetStandardDeviation() / 1000); + } + if (totalLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", totalLatencyHistogram.GetAvg() / 1000); + _Print("%.3f\n", totalLatencyHistogram.GetStandardDeviation() / 1000); + } + + _Print("\n"); + _Print("0\n"); + if (readLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", readLatencyHistogram.GetMin() / 1000); + } + if (writeLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", writeLatencyHistogram.GetMin() / 1000); + } + if (totalLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", totalLatencyHistogram.GetMin() / 1000); + } + _Print("\n"); + + // Construct vector of percentiles and decimal precision to squelch trailing zeroes. This is more + // detailed than summary text output, and does not contain the decorated names (15th, etc.) + + vector> vPercentiles; + for (int p = 1; p <= 99; p++) + { + vPercentiles.push_back(make_pair(0, p)); + } + + vPercentiles.push_back(make_pair(1, 99.9)); + vPercentiles.push_back(make_pair(2, 99.99)); + vPercentiles.push_back(make_pair(3, 99.999)); + vPercentiles.push_back(make_pair(4, 99.9999)); + vPercentiles.push_back(make_pair(5, 99.99999)); + vPercentiles.push_back(make_pair(6, 99.999999)); + + for (auto p : vPercentiles) + { + _Print("\n"); + _Print("%.*f\n", p.first, p.second); + if (readLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", readLatencyHistogram.GetPercentile(p.second / 100) / 1000); + } + if (writeLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", writeLatencyHistogram.GetPercentile(p.second / 100) / 1000); + } + if (totalLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", totalLatencyHistogram.GetPercentile(p.second / 100) / 1000); + } + _Print("\n"); + } + + _Print("\n"); + _Print("100\n"); + if (readLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", readLatencyHistogram.GetMax() / 1000); + } + if (writeLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", writeLatencyHistogram.GetMax() / 1000); + } + if (totalLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", totalLatencyHistogram.GetMax() / 1000); + } + _Print("\n"); + _Print("\n"); +} + +int XmlResultParser::GetTotalScore() +{ + return 0; +} + +double XmlResultParser::GetAverageLatency() +{ + return 0.0; +} + +string XmlResultParser::ParseResults(Profile& profile, const SystemInformation& system, vector vResults) +{ + _sResult.clear(); + + _Print("\n"); + _sResult += system.GetXml(); + _sResult += profile.GetXml(); + for (size_t iResults = 0; iResults < vResults.size(); iResults++) + { + const Results& results = vResults[iResults]; + const TimeSpan& timeSpan = profile.GetTimeSpans()[iResults]; + + _Print("\n"); + double fTime = PerfTimer::PerfTimeToSeconds(results.ullTimeCount); //test duration + if (fTime >= 0.0000001) + { + // There either is a fixed number of threads for all files to share (GetThreadCount() > 0) or a number of threads per file. + // In the latter case vThreadResults.size() == number of threads per file * file count + size_t ulThreadCnt = (timeSpan.GetThreadCount() > 0) ? timeSpan.GetThreadCount() : results.vThreadResults.size(); + size_t ulProcCount = results.vSystemProcessorPerfInfo.size(); + + _Print("%.2f\n", fTime); + _Print("%u\n", ulThreadCnt); + _Print("%u\n", ulProcCount); + + _PrintCpuUtilization(results); + + if (timeSpan.GetMeasureLatency()) + { + _PrintLatencyPercentiles(results); + } + + if (timeSpan.GetCalculateIopsStdDev()) + { + _PrintOverallIops(results, timeSpan.GetIoBucketDurationInMilliseconds()); + } + + if (results.fUseETW) + { + _PrintETW(results.EtwMask, results.EtwEventCounters); + _PrintETWSessionInfo(results.EtwSessionInfo); + } + + for (size_t iThread = 0; iThread < results.vThreadResults.size(); iThread++) + { + const ThreadResults& threadResults = results.vThreadResults[iThread]; + _Print("\n"); + _Print("%u\n", iThread); + for (const auto& targetResults : threadResults.vTargetResults) + { + _Print("\n"); + _PrintTargetResults(targetResults); + if (timeSpan.GetMeasureLatency()) + { + _PrintTargetLatency(targetResults); + } + if (timeSpan.GetCalculateIopsStdDev()) + { + _PrintTargetIops(targetResults.readBucketizer, targetResults.writeBucketizer, timeSpan.GetIoBucketDurationInMilliseconds()); + } + _Print("\n"); + } + _Print("\n"); + } + } + else + { + _Print("The test was interrupted before the measurements began. No results are displayed.\n"); + } + _Print("\n"); + } + _Print(""); + return _sResult; +} \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/XmlResultParser/xmlresultparser.h b/CristalDiskMark/source/diskspd2_0_15a/XmlResultParser/xmlresultparser.h new file mode 100644 index 0000000..b55a47e --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/XmlResultParser/xmlresultparser.h @@ -0,0 +1,53 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#include "Common.h" + +class XmlResultParser: public IResultParser +{ +public: + string ParseResults(Profile& profile, const SystemInformation& system, vector vResults); + int GetTotalScore(); + double GetAverageLatency(); + +private: + void _PrintCpuUtilization(const Results& results); + void _PrintETW(struct ETWMask ETWMask, struct ETWEventCounters EtwEventCounters); + void _PrintETWSessionInfo(struct ETWSessionInfo sessionInfo); + void _PrintLatencyPercentiles(const Results& results); + void _PrintTargetResults(const TargetResults& results); + void _PrintTargetLatency(const TargetResults& results); + void _PrintTargetIops(const IoBucketizer& readBucketizer, const IoBucketizer& writeBucketizer, UINT32 bucketTimeInMs); + void _PrintOverallIops(const Results& results, UINT32 bucketTimeInMs); + void _PrintIops(const IoBucketizer& readBucketizer, const IoBucketizer& writeBucketizer, UINT32 bucketTimeInMs); + void _Print(const char *format, ...); + + string _sResult; +}; \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/CmdLineParser/CmdLineParser.vcxproj b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/CmdLineParser/CmdLineParser.vcxproj new file mode 100644 index 0000000..5d05051 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/CmdLineParser/CmdLineParser.vcxproj @@ -0,0 +1,153 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {0EF5CE78-8E92-4A1B-A255-0F544AADA291} + CmdLineParser + 8.1 + + + + StaticLibrary + true + v140 + MultiByte + + + StaticLibrary + true + v140 + MultiByte + + + StaticLibrary + false + v140 + true + MultiByte + + + StaticLibrary + false + v140 + true + MultiByte + + + + + + + + + + + + + + + + + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + + Level3 + Disabled + true + false + true + + + true + + + false + + + + + Level3 + Disabled + true + false + true + + + true + + + true + + + + + Level3 + MaxSpeed + true + true + true + true + MultiThreaded + NoExtensions + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + MultiThreaded + + + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/CmdLineParser/CmdLineParser.vcxproj.user b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/CmdLineParser/CmdLineParser.vcxproj.user new file mode 100644 index 0000000..6fb136b --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/CmdLineParser/CmdLineParser.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/CmdRequestCreator/CmdRequestCreator.vcxproj b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/CmdRequestCreator/CmdRequestCreator.vcxproj new file mode 100644 index 0000000..b643762 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/CmdRequestCreator/CmdRequestCreator.vcxproj @@ -0,0 +1,166 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0} + CmdRequestCreator + 8.1 + + + + Application + true + v141 + MultiByte + + + Application + true + v141 + MultiByte + + + Application + false + v140 + true + MultiByte + + + Application + false + v140 + true + MultiByte + + + + + + + + + + + + + + + + + + + $(SolutionDir)\..\Common;$(IncludePath) + diskspd + + + $(SolutionDir)\..\Common;$(IncludePath) + diskspd + false + + + $(SolutionDir)\..\Common;$(IncludePath) + diskspd32 + + + $(SolutionDir)\..\Common;$(IncludePath) + diskspd64 + + + + Level3 + Disabled + true + false + true + + + true + $(SolutionDir)$(Configuration)\xmlprofileparser.lib;$(SolutionDir)$(Configuration)\iorequestgenerator.lib;$(SolutionDir)$(Configuration)\resultparser.lib;$(SolutionDir)$(Configuration)\xmlresultparser.lib;$(SolutionDir)$(Configuration)\common.lib;msxml6.lib;%(AdditionalDependencies) + Default + + + + + Level3 + Disabled + true + false + true + + + true + $(SolutionDir)$(Platform)\$(Configuration)\xmlprofileparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\iorequestgenerator.lib;$(SolutionDir)$(Platform)\$(Configuration)\resultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\xmlresultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\common.lib;msxml6.lib;%(AdditionalDependencies) + Default + + + + + Level3 + MaxSpeed + true + true + true + true + MultiThreaded + NoExtensions + + + true + true + true + fileextd.lib;$(SolutionDir)$(Configuration)\xmlprofileparser.lib;$(SolutionDir)$(Configuration)\iorequestgenerator.lib;$(SolutionDir)$(Configuration)\resultparser.lib;$(SolutionDir)$(Configuration)\xmlresultparser.lib;$(SolutionDir)$(Configuration)\common.lib;msxml6.lib;%(AdditionalDependencies) + /SUBSYSTEM:CONSOLE,5.01 %(AdditionalOptions) + + + + + Level3 + MaxSpeed + true + true + true + true + MultiThreaded + + + true + true + true + fileextd.lib;$(SolutionDir)$(Platform)\$(Configuration)\xmlprofileparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\iorequestgenerator.lib;$(SolutionDir)$(Platform)\$(Configuration)\resultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\xmlresultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\common.lib;msxml6.lib;%(AdditionalDependencies) + /SUBSYSTEM:CONSOLE,5.02 %(AdditionalOptions) + + + + + {0ef5ce78-8e92-4a1b-a255-0f544aada291} + + + + + + + + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/CmdRequestCreator/CmdRequestCreator.vcxproj.user b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/CmdRequestCreator/CmdRequestCreator.vcxproj.user new file mode 100644 index 0000000..6e2aec7 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/CmdRequestCreator/CmdRequestCreator.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/Common/Common.vcxproj b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/Common/Common.vcxproj new file mode 100644 index 0000000..b462f80 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/Common/Common.vcxproj @@ -0,0 +1,145 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {B253AB42-F482-417A-82CE-EDAFCD26F366} + Common + 8.1 + + + + StaticLibrary + true + v141 + MultiByte + + + StaticLibrary + true + v141 + MultiByte + + + StaticLibrary + false + v140 + true + MultiByte + + + StaticLibrary + false + v140 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + false + true + + + true + + + false + + + + + Level3 + Disabled + true + false + true + + + true + + + true + + + + + Level3 + MaxSpeed + true + true + true + true + MultiThreaded + NoExtensions + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + MultiThreaded + + + true + true + true + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/IORequestGenerator/IORequestGenerator.vcxproj b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/IORequestGenerator/IORequestGenerator.vcxproj new file mode 100644 index 0000000..a6ce440 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/IORequestGenerator/IORequestGenerator.vcxproj @@ -0,0 +1,163 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {62DB1E99-FBA0-45FD-9355-423059BA03B8} + IORequestGenerator + 8.1 + + + + StaticLibrary + true + v141 + MultiByte + + + StaticLibrary + true + v141 + MultiByte + + + StaticLibrary + false + v140 + true + MultiByte + + + StaticLibrary + false + v140 + true + MultiByte + + + + + + + + + + + + + + + + + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + + Level3 + Disabled + true + false + true + + + true + E:\fbl_ph_dev01\sdktools\phtools\diskspd\IORequestGenerator\IORequestGenerator.def + + + false + + + + + Level3 + Disabled + true + false + true + + + true + E:\fbl_ph_dev01\sdktools\phtools\diskspd\IORequestGenerator\IORequestGenerator.def + + + true + + + + + Level3 + MaxSpeed + true + true + true + true + MultiThreaded + NoExtensions + + + true + true + true + E:\fbl_ph_dev01\sdktools\phtools\diskspd\IORequestGenerator\IORequestGenerator.def + + + + + Level3 + MaxSpeed + true + true + true + true + MultiThreaded + + + true + true + true + E:\fbl_ph_dev01\sdktools\phtools\diskspd\IORequestGenerator\IORequestGenerator.def + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/ResultParser/ResultParser.vcxproj b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/ResultParser/ResultParser.vcxproj new file mode 100644 index 0000000..4a36b11 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/ResultParser/ResultParser.vcxproj @@ -0,0 +1,157 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {F6C211DC-B076-4716-BCDC-D7DE88973B66} + ResultParser + 8.1 + + + + StaticLibrary + true + v141 + MultiByte + + + StaticLibrary + true + v141 + MultiByte + + + StaticLibrary + false + v140 + true + MultiByte + + + StaticLibrary + false + v140 + true + MultiByte + + + + + + + + + + + + + + + + + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + + Level3 + Disabled + true + false + true + + + true + E:\fbl_ph_dev01\sdktools\phtools\diskspd\ResultParser\ResultParser.def + + + false + + + + + Level3 + Disabled + true + false + true + + + true + E:\fbl_ph_dev01\sdktools\phtools\diskspd\ResultParser\ResultParser.def + + + true + + + + + Level3 + MaxSpeed + true + true + true + true + MultiThreaded + NoExtensions + + + true + true + true + E:\fbl_ph_dev01\sdktools\phtools\diskspd\ResultParser\ResultParser.def + + + + + Level3 + MaxSpeed + true + true + true + true + MultiThreaded + + + true + true + true + E:\fbl_ph_dev01\sdktools\phtools\diskspd\ResultParser\ResultParser.def + + + + + + + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/XmlProfileParser/XmlProfileParser.vcxproj b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/XmlProfileParser/XmlProfileParser.vcxproj new file mode 100644 index 0000000..f48ee8d --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/XmlProfileParser/XmlProfileParser.vcxproj @@ -0,0 +1,159 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {EFF06674-B068-45F1-9661-DB9363B025B3} + XmlProfileParser + 8.1 + + + + StaticLibrary + true + v141 + MultiByte + + + StaticLibrary + true + v141 + MultiByte + + + StaticLibrary + false + v140 + true + MultiByte + + + StaticLibrary + false + v140 + true + MultiByte + + + + + + + + + + + + + + + + + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + + Level3 + Disabled + true + false + true + + + true + + + false + + + + + Level3 + Disabled + true + false + true + + + true + + + true + + + + + Level3 + MaxSpeed + true + true + true + true + MultiThreaded + NoExtensions + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + MultiThreaded + + + true + true + true + + + + + + ..\..\XmlProfileParser\diskspd.xsd + + + + + + + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/XmlProfileParser/diskspd.h b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/XmlProfileParser/diskspd.h new file mode 100644 index 0000000..860dfa1 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/XmlProfileParser/diskspd.h @@ -0,0 +1,277 @@ +#pragma once + +#using +#using +#using +#using + +using namespace System::Security::Permissions; +[assembly:SecurityPermissionAttribute(SecurityAction::RequestMinimum, SkipVerification=false)]; +// +// このソース コードは xsd によって自動生成されました。Version=4.6.1055.0 です。 +// +namespace XmlProfileParser { + using namespace System; + ref class NewDataSet; + + + /// +///Represents a strongly typed in-memory cache of data. +/// + [System::Serializable, + System::ComponentModel::DesignerCategoryAttribute(L"code"), + System::ComponentModel::ToolboxItem(true), + System::Xml::Serialization::XmlSchemaProviderAttribute(L"GetTypedDataSetSchema"), + System::Xml::Serialization::XmlRootAttribute(L"NewDataSet"), + System::ComponentModel::Design::HelpKeywordAttribute(L"vs.data.DataSet")] + public ref class NewDataSet : public ::System::Data::DataSet { + + private: ::System::Data::SchemaSerializationMode _schemaSerializationMode; + + public: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + NewDataSet(); + protected: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + NewDataSet(::System::Runtime::Serialization::SerializationInfo^ info, ::System::Runtime::Serialization::StreamingContext context); + public: [System::Diagnostics::DebuggerNonUserCodeAttribute, + System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0"), + System::ComponentModel::BrowsableAttribute(true), + System::ComponentModel::DesignerSerializationVisibilityAttribute(::System::ComponentModel::DesignerSerializationVisibility::Visible)] + virtual property ::System::Data::SchemaSerializationMode SchemaSerializationMode { + ::System::Data::SchemaSerializationMode get() override; + System::Void set(::System::Data::SchemaSerializationMode value) override; + } + + public: [System::Diagnostics::DebuggerNonUserCodeAttribute, + System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0"), + System::ComponentModel::DesignerSerializationVisibilityAttribute(::System::ComponentModel::DesignerSerializationVisibility::Hidden)] + property ::System::Data::DataTableCollection^ Tables { + ::System::Data::DataTableCollection^ get() new; + } + + public: [System::Diagnostics::DebuggerNonUserCodeAttribute, + System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0"), + System::ComponentModel::DesignerSerializationVisibilityAttribute(::System::ComponentModel::DesignerSerializationVisibility::Hidden)] + property ::System::Data::DataRelationCollection^ Relations { + ::System::Data::DataRelationCollection^ get() new; + } + + protected: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + virtual ::System::Void InitializeDerivedDataSet() override; + + public: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + virtual ::System::Data::DataSet^ Clone() override; + + protected: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + virtual ::System::Boolean ShouldSerializeTables() override; + + protected: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + virtual ::System::Boolean ShouldSerializeRelations() override; + + protected: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + virtual ::System::Void ReadXmlSerializable(::System::Xml::XmlReader^ reader) override; + + protected: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + virtual ::System::Xml::Schema::XmlSchema^ GetSchemaSerializable() override; + + internal: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + ::System::Void InitVars(); + + internal: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + ::System::Void InitVars(::System::Boolean initTable); + + private: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + ::System::Void InitClass(); + + private: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + ::System::Void SchemaChanged(::System::Object^ sender, ::System::ComponentModel::CollectionChangeEventArgs^ e); + + public: [System::Diagnostics::DebuggerNonUserCodeAttribute] + [System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.Data.Design.TypedDataSetGenerator", L"4.0.0.0")] + static ::System::Xml::Schema::XmlSchemaComplexType^ GetTypedDataSetSchema(::System::Xml::Schema::XmlSchemaSet^ xs); + }; +} +namespace XmlProfileParser { + + + inline NewDataSet::NewDataSet() { + this->BeginInit(); + this->InitClass(); + ::System::ComponentModel::CollectionChangeEventHandler^ schemaChangedHandler = gcnew ::System::ComponentModel::CollectionChangeEventHandler(this, &XmlProfileParser::NewDataSet::SchemaChanged); + __super::Tables->CollectionChanged += schemaChangedHandler; + __super::Relations->CollectionChanged += schemaChangedHandler; + this->EndInit(); + } + + inline NewDataSet::NewDataSet(::System::Runtime::Serialization::SerializationInfo^ info, ::System::Runtime::Serialization::StreamingContext context) : + ::System::Data::DataSet(info, context, false) { + if (this->IsBinarySerialized(info, context) == true) { + this->InitVars(false); + ::System::ComponentModel::CollectionChangeEventHandler^ schemaChangedHandler1 = gcnew ::System::ComponentModel::CollectionChangeEventHandler(this, &XmlProfileParser::NewDataSet::SchemaChanged); + this->Tables->CollectionChanged += schemaChangedHandler1; + this->Relations->CollectionChanged += schemaChangedHandler1; + return; + } + ::System::String^ strSchema = (cli::safe_cast<::System::String^ >(info->GetValue(L"XmlSchema", ::System::String::typeid))); + if (this->DetermineSchemaSerializationMode(info, context) == ::System::Data::SchemaSerializationMode::IncludeSchema) { + ::System::Data::DataSet^ ds = (gcnew ::System::Data::DataSet()); + ds->ReadXmlSchema((gcnew ::System::Xml::XmlTextReader((gcnew ::System::IO::StringReader(strSchema))))); + this->DataSetName = ds->DataSetName; + this->Prefix = ds->Prefix; + this->Namespace = ds->Namespace; + this->Locale = ds->Locale; + this->CaseSensitive = ds->CaseSensitive; + this->EnforceConstraints = ds->EnforceConstraints; + this->Merge(ds, false, ::System::Data::MissingSchemaAction::Add); + this->InitVars(); + } + else { + this->ReadXmlSchema((gcnew ::System::Xml::XmlTextReader((gcnew ::System::IO::StringReader(strSchema))))); + } + this->GetSerializationData(info, context); + ::System::ComponentModel::CollectionChangeEventHandler^ schemaChangedHandler = gcnew ::System::ComponentModel::CollectionChangeEventHandler(this, &XmlProfileParser::NewDataSet::SchemaChanged); + __super::Tables->CollectionChanged += schemaChangedHandler; + this->Relations->CollectionChanged += schemaChangedHandler; + } + + inline ::System::Data::SchemaSerializationMode NewDataSet::SchemaSerializationMode::get() { + return this->_schemaSerializationMode; + } + inline System::Void NewDataSet::SchemaSerializationMode::set(::System::Data::SchemaSerializationMode value) { + this->_schemaSerializationMode = __identifier(value); + } + + inline ::System::Data::DataTableCollection^ NewDataSet::Tables::get() { + return __super::Tables; + } + + inline ::System::Data::DataRelationCollection^ NewDataSet::Relations::get() { + return __super::Relations; + } + + inline ::System::Void NewDataSet::InitializeDerivedDataSet() { + this->BeginInit(); + this->InitClass(); + this->EndInit(); + } + + inline ::System::Data::DataSet^ NewDataSet::Clone() { + XmlProfileParser::NewDataSet^ cln = (cli::safe_cast(__super::Clone())); + cln->InitVars(); + cln->SchemaSerializationMode = this->SchemaSerializationMode; + return cln; + } + + inline ::System::Boolean NewDataSet::ShouldSerializeTables() { + return false; + } + + inline ::System::Boolean NewDataSet::ShouldSerializeRelations() { + return false; + } + + inline ::System::Void NewDataSet::ReadXmlSerializable(::System::Xml::XmlReader^ reader) { + if (this->DetermineSchemaSerializationMode(reader) == ::System::Data::SchemaSerializationMode::IncludeSchema) { + this->Reset(); + ::System::Data::DataSet^ ds = (gcnew ::System::Data::DataSet()); + ds->ReadXml(reader); + this->DataSetName = ds->DataSetName; + this->Prefix = ds->Prefix; + this->Namespace = ds->Namespace; + this->Locale = ds->Locale; + this->CaseSensitive = ds->CaseSensitive; + this->EnforceConstraints = ds->EnforceConstraints; + this->Merge(ds, false, ::System::Data::MissingSchemaAction::Add); + this->InitVars(); + } + else { + this->ReadXml(reader); + this->InitVars(); + } + } + + inline ::System::Xml::Schema::XmlSchema^ NewDataSet::GetSchemaSerializable() { + ::System::IO::MemoryStream^ stream = (gcnew ::System::IO::MemoryStream()); + this->WriteXmlSchema((gcnew ::System::Xml::XmlTextWriter(stream, nullptr))); + stream->Position = 0; + return ::System::Xml::Schema::XmlSchema::Read((gcnew ::System::Xml::XmlTextReader(stream)), nullptr); + } + + inline ::System::Void NewDataSet::InitVars() { + this->InitVars(true); + } + + inline ::System::Void NewDataSet::InitVars(::System::Boolean initTable) { + } + + inline ::System::Void NewDataSet::InitClass() { + this->DataSetName = L"NewDataSet"; + this->Prefix = L""; + this->Namespace = L"http://microsoft.com/diskspd/DiskSpdConfig.xsd"; + this->Locale = (gcnew ::System::Globalization::CultureInfo(L"")); + this->EnforceConstraints = true; + this->SchemaSerializationMode = ::System::Data::SchemaSerializationMode::IncludeSchema; + } + + inline ::System::Void NewDataSet::SchemaChanged(::System::Object^ sender, ::System::ComponentModel::CollectionChangeEventArgs^ e) { + if (e->Action == ::System::ComponentModel::CollectionChangeAction::Remove) { + this->InitVars(); + } + } + + inline ::System::Xml::Schema::XmlSchemaComplexType^ NewDataSet::GetTypedDataSetSchema(::System::Xml::Schema::XmlSchemaSet^ xs) { + XmlProfileParser::NewDataSet^ ds = (gcnew XmlProfileParser::NewDataSet()); + ::System::Xml::Schema::XmlSchemaComplexType^ type = (gcnew ::System::Xml::Schema::XmlSchemaComplexType()); + ::System::Xml::Schema::XmlSchemaSequence^ sequence = (gcnew ::System::Xml::Schema::XmlSchemaSequence()); + ::System::Xml::Schema::XmlSchemaAny^ any = (gcnew ::System::Xml::Schema::XmlSchemaAny()); + any->Namespace = ds->Namespace; + sequence->Items->Add(any); + type->Particle = sequence; + ::System::Xml::Schema::XmlSchema^ dsSchema = ds->GetSchemaSerializable(); + if (xs->Contains(dsSchema->TargetNamespace)) { + ::System::IO::MemoryStream^ s1 = (gcnew ::System::IO::MemoryStream()); + ::System::IO::MemoryStream^ s2 = (gcnew ::System::IO::MemoryStream()); + try { + ::System::Xml::Schema::XmlSchema^ schema = nullptr; + dsSchema->Write(s1); + for ( ::System::Collections::IEnumerator^ schemas = xs->Schemas(dsSchema->TargetNamespace)->GetEnumerator(); schemas->MoveNext(); ) { + schema = (cli::safe_cast<::System::Xml::Schema::XmlSchema^ >(schemas->Current)); + s2->SetLength(0); + schema->Write(s2); + if (s1->Length == s2->Length) { + s1->Position = 0; + s2->Position = 0; + for ( ; ((s1->Position != s1->Length) + && (s1->ReadByte() == s2->ReadByte())); ) { + ; + } + if (s1->Position == s1->Length) { + return type; + } + } + } + } + finally { + if (s1 != nullptr) { + s1->Close(); + } + if (s2 != nullptr) { + s2->Close(); + } + } + } + xs->Add(dsSchema); + return type; + } +} diff --git a/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/XmlResultParser/XmlResultParser.vcxproj b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/XmlResultParser/XmlResultParser.vcxproj new file mode 100644 index 0000000..dbaa5a2 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/XmlResultParser/XmlResultParser.vcxproj @@ -0,0 +1,170 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {60A28E9C-C245-4D99-9C1C-EC911031743F} + Win32Proj + XmlResultParser + 8.1 + + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + false + v140 + true + Unicode + + + StaticLibrary + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + false + true + + + Windows + true + + + false + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + false + true + + + Windows + true + + + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + NoExtensions + + + Windows + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + Windows + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/diskspd_vs2013.v12.suo b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/diskspd_vs2013.v12.suo new file mode 100644 index 0000000..712b7a0 Binary files /dev/null and b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/diskspd_vs2013.v12.suo differ diff --git a/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/diskspd_vs2015.sln b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/diskspd_vs2015.sln new file mode 100644 index 0000000..69af998 --- /dev/null +++ b/CristalDiskMark/source/diskspd2_0_15a/diskspd_vs2015/diskspd_vs2015.sln @@ -0,0 +1,96 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.30723.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CmdLineParser", "CmdLineParser\CmdLineParser.vcxproj", "{0EF5CE78-8E92-4A1B-A255-0F544AADA291}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CmdRequestCreator", "CmdRequestCreator\CmdRequestCreator.vcxproj", "{D238F8AA-DE12-49E7-B4A7-9B69579A69C0}" + ProjectSection(ProjectDependencies) = postProject + {B253AB42-F482-417A-82CE-EDAFCD26F366} = {B253AB42-F482-417A-82CE-EDAFCD26F366} + {EFF06674-B068-45F1-9661-DB9363B025B3} = {EFF06674-B068-45F1-9661-DB9363B025B3} + {0EF5CE78-8E92-4A1B-A255-0F544AADA291} = {0EF5CE78-8E92-4A1B-A255-0F544AADA291} + {62DB1E99-FBA0-45FD-9355-423059BA03B8} = {62DB1E99-FBA0-45FD-9355-423059BA03B8} + {60A28E9C-C245-4D99-9C1C-EC911031743F} = {60A28E9C-C245-4D99-9C1C-EC911031743F} + {F6C211DC-B076-4716-BCDC-D7DE88973B66} = {F6C211DC-B076-4716-BCDC-D7DE88973B66} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IORequestGenerator", "IORequestGenerator\IORequestGenerator.vcxproj", "{62DB1E99-FBA0-45FD-9355-423059BA03B8}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ResultParser", "ResultParser\ResultParser.vcxproj", "{F6C211DC-B076-4716-BCDC-D7DE88973B66}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "XmlProfileParser", "XmlProfileParser\XmlProfileParser.vcxproj", "{EFF06674-B068-45F1-9661-DB9363B025B3}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "Common\Common.vcxproj", "{B253AB42-F482-417A-82CE-EDAFCD26F366}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "XmlResultParser", "XmlResultParser\XmlResultParser.vcxproj", "{60A28E9C-C245-4D99-9C1C-EC911031743F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Debug|Win32.ActiveCfg = Debug|Win32 + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Debug|Win32.Build.0 = Debug|Win32 + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Debug|x64.ActiveCfg = Debug|x64 + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Debug|x64.Build.0 = Debug|x64 + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Release|Win32.ActiveCfg = Release|Win32 + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Release|Win32.Build.0 = Release|Win32 + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Release|x64.ActiveCfg = Release|x64 + {0EF5CE78-8E92-4A1B-A255-0F544AADA291}.Release|x64.Build.0 = Release|x64 + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Debug|Win32.ActiveCfg = Debug|Win32 + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Debug|Win32.Build.0 = Debug|Win32 + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Debug|x64.ActiveCfg = Debug|x64 + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Debug|x64.Build.0 = Debug|x64 + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Release|Win32.ActiveCfg = Release|Win32 + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Release|Win32.Build.0 = Release|Win32 + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Release|x64.ActiveCfg = Release|x64 + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0}.Release|x64.Build.0 = Release|x64 + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Debug|Win32.ActiveCfg = Debug|Win32 + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Debug|Win32.Build.0 = Debug|Win32 + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Debug|x64.ActiveCfg = Debug|x64 + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Debug|x64.Build.0 = Debug|x64 + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Release|Win32.ActiveCfg = Release|Win32 + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Release|Win32.Build.0 = Release|Win32 + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Release|x64.ActiveCfg = Release|x64 + {62DB1E99-FBA0-45FD-9355-423059BA03B8}.Release|x64.Build.0 = Release|x64 + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Debug|Win32.ActiveCfg = Debug|Win32 + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Debug|Win32.Build.0 = Debug|Win32 + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Debug|x64.ActiveCfg = Debug|x64 + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Debug|x64.Build.0 = Debug|x64 + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Release|Win32.ActiveCfg = Release|Win32 + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Release|Win32.Build.0 = Release|Win32 + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Release|x64.ActiveCfg = Release|x64 + {F6C211DC-B076-4716-BCDC-D7DE88973B66}.Release|x64.Build.0 = Release|x64 + {EFF06674-B068-45F1-9661-DB9363B025B3}.Debug|Win32.ActiveCfg = Debug|Win32 + {EFF06674-B068-45F1-9661-DB9363B025B3}.Debug|Win32.Build.0 = Debug|Win32 + {EFF06674-B068-45F1-9661-DB9363B025B3}.Debug|x64.ActiveCfg = Debug|x64 + {EFF06674-B068-45F1-9661-DB9363B025B3}.Debug|x64.Build.0 = Debug|x64 + {EFF06674-B068-45F1-9661-DB9363B025B3}.Release|Win32.ActiveCfg = Release|Win32 + {EFF06674-B068-45F1-9661-DB9363B025B3}.Release|Win32.Build.0 = Release|Win32 + {EFF06674-B068-45F1-9661-DB9363B025B3}.Release|x64.ActiveCfg = Release|x64 + {EFF06674-B068-45F1-9661-DB9363B025B3}.Release|x64.Build.0 = Release|x64 + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Debug|Win32.ActiveCfg = Debug|Win32 + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Debug|Win32.Build.0 = Debug|Win32 + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Debug|x64.ActiveCfg = Debug|x64 + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Debug|x64.Build.0 = Debug|x64 + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Release|Win32.ActiveCfg = Release|Win32 + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Release|Win32.Build.0 = Release|Win32 + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Release|x64.ActiveCfg = Release|x64 + {B253AB42-F482-417A-82CE-EDAFCD26F366}.Release|x64.Build.0 = Release|x64 + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Debug|Win32.ActiveCfg = Debug|Win32 + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Debug|Win32.Build.0 = Debug|Win32 + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Debug|x64.ActiveCfg = Debug|x64 + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Debug|x64.Build.0 = Debug|x64 + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Release|Win32.ActiveCfg = Release|Win32 + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Release|Win32.Build.0 = Release|Win32 + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Release|x64.ActiveCfg = Release|x64 + {60A28E9C-C245-4D99-9C1C-EC911031743F}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/CristalDiskMark/source/diskspd2_0_15a/exe/DiskSpd32L.exe b/CristalDiskMark/source/diskspd2_0_15a/exe/DiskSpd32L.exe new file mode 100644 index 0000000..b5cd680 Binary files /dev/null and b/CristalDiskMark/source/diskspd2_0_15a/exe/DiskSpd32L.exe differ diff --git a/CristalDiskMark/source/diskspd2_0_15a/exe/DiskSpd64L.exe b/CristalDiskMark/source/diskspd2_0_15a/exe/DiskSpd64L.exe new file mode 100644 index 0000000..2b714e9 Binary files /dev/null and b/CristalDiskMark/source/diskspd2_0_15a/exe/DiskSpd64L.exe differ