Disk Drive Activity Monitor

Windows specific questions.
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Disk Drive Activity Monitor

Post by MichaelW »

This utilizes the Performance Data Helper Functions and Shell_NotifyIcon. Because it depends on PDH, this version requires Windows 2000 or later (or Windows NT 4.0 with pdh.dll added).

Code: Select all

''
'' This app displays a simple disk drive activity indicator in
'' the Taskbar notification area (a.k.a. system tray). Requires
'' Windows 2000 or later (or Windows NT 4.0 with pdh.dll added).
''
#include once "windows.bi"
#include once "win/shellapi.bi"
#include "pdh.bi"
option explicit

'*************************************************************************
#define WM_CALLBACK       WM_USER+100
#define IDI_TASKBARICON   0
#define IDM_EXIT          1000
#define ID_TIMER          1

dim shared hPopupMenu as HMENU
dim shared hQueryR    as PDH_HQUERY
dim shared hQueryW    as PDH_HQUERY
dim shared hCounterR  as PDH_HCOUNTER
dim shared hCounterW  as PDH_HCOUNTER
dim shared hIconRW    as HICON
dim shared hIconNA    as HICON
dim shared hIconR     as HICON
dim shared hIconW     as HICON

dim shared nid        as NOTIFYICONDATA
dim shared pfcR       as PDH_FMT_COUNTERVALUE
dim shared pfcw       as PDH_FMT_COUNTERVALUE

sub TimerProc

    static as uint queryCountR, queryCountW
    static as uint queryTotalR, queryTotalW
    static as uint prevStateR,  prevStateW

    dim as uint stateR, stateW, fChange

    '' To ensure that the display states will persist long
    '' enough to be readily visible, sum the counter values
    '' over 10 timer periods (so the display states will
    '' persist for at least 100ms).

    PdhCollectQueryData( hQueryR )
    PdhGetFormattedCounterValue( hCounterR, PDH_FMT_LONG, 0, @pfcR )
    if pfcR.CStatus = PDH_CSTATUS_VALID_DATA then
        queryCountR += 1
        queryTotalR += pfcR.longValue
        if queryCountR = 10 then
            stateR = queryTotalR > 0
            queryCountR = 0
            queryTotalR = 0
            if prevStateR <> stateR then
                prevStateR = stateR
                fChange = true
            endif
        endif
    endif

    PdhCollectQueryData( hQueryW )
    PdhGetFormattedCounterValue( hCounterW, PDH_FMT_LONG, 0, @pfcW )
    if pfcW.CStatus = PDH_CSTATUS_VALID_DATA then
        queryCountW += 1
        queryTotalW += pfcW.longValue
        if queryCountW = 10 then
            stateW = queryTotalW > 0
            queryCountW = 0
            queryTotalW = 0
            if prevStateW <> stateW then
                prevStateW = stateW
                fChange = true
            endif
        endif
    endif

    if fChange then
        if stateR and stateW then
            nid.hIcon = hIconRW
        elseif stateR then
            nid.hIcon = hIconR
        elseif stateW then
            nid.hIcon = hIconW
        else
            nid.hIcon = hIconNA
        endif
        Shell_NotifyIcon( NIM_MODIFY, @nid )
    endif

end sub

function WindowProc( byval hWnd as HWND, _
                     byval uMsg as UINT, _
                     byval wParam as WPARAM, _
                     byval lParam as LPARAM ) as LRESULT

    dim hMod as HMODULE
    dim pt as POINT

    select case uMsg

        case WM_CREATE

            hMod = GetModuleHandle( null )

            hIconRW = LoadImage( hMod, MAKEINTRESOURCEA(1000), _
                                 IMAGE_ICON, 0, 0, null )

            hIconR =  LoadImage( hMod, MAKEINTRESOURCEA(2000), _
                                 IMAGE_ICON, 0, 0, null )

            hIconW =  LoadImage( hMod, MAKEINTRESOURCEA(3000), _
                                 IMAGE_ICON, 0, 0, null )

            hIconNA = LoadImage( hMod, MAKEINTRESOURCEA(4000), _
                                 IMAGE_ICON, 0, 0, null )

            hPopupMenu = CreatePopupMenu
            AppendMenu( hPopupMenu, MF_STRING, IDM_EXIT, "Exit" )
            nid.cbSize = len( NOTIFYICONDATA )
            nid.hwnd = hWnd
            nid.uID = IDI_TASKBARICON
            nid.uFlags = NIF_ICON + NIF_MESSAGE + NIF_TIP
            nid.uCallbackMessage = WM_CALLBACK
            nid.hIcon = hIconNA
            nid.szTip = "HDD Activity Monitor"
            Shell_NotifyIcon( NIM_ADD, @nid )
            return 0

        case WM_DESTROY

            PostQuitMessage( null )
            return 0

        case WM_TIMER

            TimerProc
            return 0

        case WM_COMMAND

            if lParam = 0 then
                Shell_NotifyIcon( NIM_DELETE, @nid )
                if wParam = IDM_EXIT then
                    DestroyWindow( hWnd )
                endif
            endif
            return 0

        case WM_CALLBACK

            if wParam = IDI_TASKBARICON then
                if lParam = WM_RBUTTONDOWN then

                '' To avoid a problem with the menu not closing
                '' when the user clicks somewhere else our window
                '' must be the foreground window.
                ''
                SetForegroundWindow( hWnd )
                GetCursorPos( @pt )
                TrackPopupMenu( hPopupMenu, TPM_RIGHTALIGN, _
                                pt.x, pt.y, null, hWnd, null )
                endif
            endif
            return 0

        case else

            return DefWindowProc( hWnd, uMsg, wParam, lParam )

    end select

end function

function WinMain( byval hInstance as HINSTANCE,_
                  byval hPrevInstance as HINSTANCE,_
                  lpCmdLine as string,_
                  byval nCmdShow as integer )

    dim hWnd          as HWND
    dim nSize         as DWORD
    dim wMsg          as MSG
    dim wcx           as WNDCLASSEX
    dim className     as string = "HddActivityMonitor_class"
    dim machineName   as string * MAX_COMPUTERNAME_LENGTH + 1
    dim objectName    as string * 128
    dim counterNameR  as string * 128
    dim counterNameW  as string * 128
    dim instanceName  as string = "_Total"
    dim counterPath   as string * PDH_MAX_COUNTER_PATH
    dim pcpe          as PDH_COUNTER_PATH_ELEMENTS

    nSize = MAX_COMPUTERNAME_LENGTH + 1

    GetComputerName( machineName, @nSize )

    '' Lookup the object and counter names by index so the object
    '' and counters will be correctly specified on non-English
    '' language systems. For whatever reason, instance names are
    '' English only.

    '' Index 234 is "PhysicalDisk" on English language systems.
    ''
    nSize = 128
    PdhLookupPerfNameByIndex( strptr(machineName), 234, _
                              strptr(objectName), @nSize )

    '' Index 202 is "% Disk Read Time" on English language systems.
    ''
    nSize = 128
    PdhLookupPerfNameByIndex( strptr(machineName), 202, _
                              strptr(counterNameR), @nSize )

    ' Index 204 is "% Disk Write Time" on English language systems.
    '
    nSize = 128
    PdhLookupPerfNameByIndex( strptr(machineName), 204, _
                              strptr(counterNameW), @nSize )

    pcpe.szMachineName    = strptr( machineName )
    pcpe.szObjectName     = strptr( objectName )
    pcpe.szCounterName    = strptr( counterNameR )
    pcpe.szInstanceName   = strptr( instanceName )
    pcpe.szParentInstance = null
    pcpe.dwInstanceIndex  = -1

    nSize = PDH_MAX_COUNTER_PATH
    PdhMakeCounterPath( @pcpe, strptr( counterPath ), @nSize, 0 )

    PdhOpenQuery( null, null, @hQueryR )

    PdhAddCounter( hQueryR, strptr( counterPath ), null, @hCounterR )

    pcpe.szMachineName    = strptr( machineName )
    pcpe.szObjectName     = strptr( objectName )
    pcpe.szCounterName    = strptr( counterNameW )
    pcpe.szInstanceName   = strptr( instanceName )
    pcpe.szParentInstance = null
    pcpe.dwInstanceIndex  = -1

    nSize = PDH_MAX_COUNTER_PATH
    PdhMakeCounterPath( @pcpe, strptr( counterPath ), @nSize, 0 )

    PdhOpenQuery( null, null, @hQueryW )

    PdhAddCounter( hQueryW, strptr( counterPath ), null, @hCounterW )

    with wcx
        .cbSize         = len( WNDCLASSEX )
        .style          = CS_HREDRAW or CS_VREDRAW or CS_BYTEALIGNWINDOW
        .lpfnWndProc    = cast( WNDPROC, @WindowProc )
        .cbClsExtra     = null
        .cbWndExtra     = null
        .hInstance      = hInstance
        .hbrBackground  = cast( HBRUSH, COLOR_WINDOW + 1 )
        .lpszMenuName   = null
        .lpszClassName  = strptr( className )
        .hIcon          = LoadIcon( null, IDI_APPLICATION )
        .hCursor        = LoadCursor( null, IDC_ARROW )
        .hIconSm        = LoadIcon( null, IDI_APPLICATION )
    end with

    RegisterClassEx( @wcx )

    hWnd = CreateWindowEx( WS_EX_OVERLAPPEDWINDOW, _
                           strptr( className ), _
                           "Hdd Activity Monitor", _
                           WS_OVERLAPPEDWINDOW, _
                           10, 10, 300, 200, _
                           null, null, hInstance, null )

    '' This for debug only.
    ''
    '' ShowWindow( hWnd, nCmdShow )

    '' Poll the counters at the maximum timer frequency to minimize
    '' the number of undetected disk activities. The processor time
    '' is negligible even at the maximum timer frequency.
    ''
    SetTimer( hWnd, ID_TIMER, 10, null )

    do until( GetMessage( @wMsg, null, 0, 0 ) = 0 )

        TranslateMessage( @wMsg )
        DispatchMessage( @wMsg )

    loop

    PdhCloseQuery( hQueryR )
    PdhCloseQuery( hQueryW )
    KillTimer( hWnd, ID_TIMER )

    return wMsg.wParam

end function

end WinMain( GetModuleHandle( null ), null, Command$, SW_NORMAL )
Resource definition:

Code: Select all

1000 ICON "rw.ico"           ; First will be application icon
2000 ICON "read.ico"
3000 ICON "write.ico"
4000 ICON "na.ico"
Module definition for PDH import library:

Code: Select all

LIBRARY PDH.DLL
EXPORTS
PdhAddCounterA@16
PdhAddCounterW@16
PdhCloseQuery@4
PdhCollectQueryData@4
PdhGetFormattedCounterValue@16
PdhLookupPerfIndexByNameA@12
PdhLookupPerfIndexByNameW@12
PdhLookupPerfNameByIndexA@16
PdhLookupPerfNameByIndexW@16
PdhMakeCounterPathA@16
PdhMakeCounterPathW@16
PdhOpenQuery@12
Batch file used to build the import library:

Code: Select all

:
: This assumes that dlltool.exe and as.exe are in the current directory
:
dlltool -k -d pdh.dll.def -l pdh.dll.a
pause
A minimal PDH include file:

Code: Select all

''
'' This file defines a *small* subset of the PDH functions and
'' related data types and structures.
''
#inclib "pdh"

#define PDH_MAX_COUNTER_PATH    2048
#define PDH_FMT_LONG            &h00000100
#define PDH_CSTATUS_VALID_DATA  0

type PDH_STATUS   as UINT
type PDH_HQUERY   as UINT
type PDH_HCOUNTER as UINT

type PDH_COUNTER_PATH_ELEMENTS
  szMachineName     as LPTSTR
  szObjectName      as LPTSTR
  szInstanceName    as LPTSTR
  szParentInstance  as LPTSTR
  dwInstanceIndex   as DWORD
  szCounterName     as LPTSTR
end type

type PDH_FMT_COUNTERVALUE
  CStatus           as DWORD
  padding           as DWORD
  union
    longValue       as LONG
    doubleValue     as DOUBLE
    largeValue      as DWORDLONG
    AnsiStringValue as LPCSTR
    WideStringValue as LPCWSTR
  end union
end type

declare function PdhAddCounter alias "PdhAddCounterA" _
                    (byval as PDH_HQUERY, byval as LPCTSTR, _
                    byval as DWORD_PTR, byval as PDH_HCOUNTER ptr) _
                    as PDH_STATUS

declare function PdhCloseQuery alias "PdhCloseQuery" _
                    (byval as PDH_HQUERY) _
                    as PDH_STATUS

declare function PdhCollectQueryData alias "PdhCollectQueryData" _
                    (byval as PDH_HQUERY) _
                    as PDH_STATUS

declare function PdhGetFormattedCounterValue alias "PdhGetFormattedCounterValue" _
                    (byval as PDH_HCOUNTER, _
                    byval as DWORD, byval as LPDWORD, _
                    byval as PDH_FMT_COUNTERVALUE ptr) _
                    as PDH_STATUS

declare function PdhLookupPerfIndexByName alias "PdhLookupPerfIndexByNameA" _
                    (byval as LPCTSTR, _
                    byval as LPCTSTR, _
                    byval as LPDWORD) _
                    as PDH_STATUS

declare function PdhLookupPerfNameByIndex alias "PdhLookupPerfNameByIndexA" _
                    (byval as LPCTSTR, _
                    byval as DWORD, _
                    byval as LPTSTR, _
                    byval as LPDWORD) _
                    as PDH_STATUS

declare function PdhMakeCounterPath alias "PdhMakeCounterPathA" _
                    (byval as PDH_COUNTER_PATH_ELEMENTS ptr, _
                    byval as LPTSTR, _
                    byval as LPDWORD, _
                    byval as DWORD) _
                    as PDH_STATUS

declare function PdhOpenQuery alias "PdhOpenQuery" _
                    (byval as LPCTSTR, _
                    byval as DWORD_PTR, _
                    byval as PDH_HQUERY ptr) _
                    as PDH_STATUS
The icons I used were 32x32 16 color, divided into right and left panels each with a 15w x 32h single pixel outline. The center of the left panel was filled with green for read activity and the center of the right filled with red for write activity, with the center of the panels transparent for no corresponding activity.
voodooattack
Posts: 605
Joined: Feb 18, 2006 13:30
Location: Alexandria / Egypt
Contact:

Post by voodooattack »

outstaning work! ^_^

5 thumbs up! ;D

i used some nice XP alpha icons, and it looks amazing :D

i can't stop watching that nifty lil icon (adds to startup) :)
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Post by MichaelW »

Thanks. I don’t even want to know you came up with five thumbs.
rdc
Posts: 1741
Joined: May 27, 2005 17:22
Location: Texas, USA
Contact:

Post by rdc »

Cool, as always. :)
voodooattack
Posts: 605
Joined: Feb 18, 2006 13:30
Location: Alexandria / Egypt
Contact:

Post by voodooattack »

MichaelW wrote:Thanks. I don’t even want to know you came up with five thumbs.
hehe :P

nah, really.. i gotta restudy that code :)

btw, i'm really sorry about the other thread.. when i came back and read it, i didn't like the way my posts sounded.. apologies.. :(
oyster
Posts: 274
Joined: Oct 11, 2005 10:46

Post by oyster »

sorry, but can anyone give some compile instruction? I am using fbc 0.16b
I saved all the file
then I have made pdh.dll.a
and then I tried
fbc.exe pdh.bas pdh.rc
but I got
pdh.bas(17) : error 14: Expected identifier, found: 'LPTSTR'

szMachineName as LPTSTR
^
pdh.bas(18) : error 14: Expected identifier, found: 'LPTSTR'

szObjectName as LPTSTR
^
pdh.bas(19) : error 14: Expected identifier, found: 'LPTSTR'

szInstanceName as LPTSTR
^
pdh.bas(20) : error 14: Expected identifier, found: 'LPTSTR'

szParentInstance as LPTSTR
^
pdh.bas(21) : error 14: Expected identifier, found: 'DWORD'

dwInstanceIndex as DWORD
^
pdh.bas(22) : error 14: Expected identifier, found: 'LPTSTR'

szCounterName as LPTSTR
^
pdh.bas(26) : error 14: Expected identifier, found: 'DWORD'

CStatus as DWORD
^
pdh.bas(27) : error 14: Expected identifier, found: 'DWORD'

padding as DWORD
^
pdh.bas(31) : error 14: Expected identifier, found: 'DWORDLONG'

largeValue as DWORDLONG
^
pdh.bas(32) : error 14: Expected identifier, found: 'LPCSTR'

AnsiStringValue as LPCSTR
thanx
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Post by MichaelW »

voodooattack wrote: btw, i'm…
No problem, I didn’t like my responses, either :(

And that was supposed to be “I don’t even want to know how you came up with five thumbs”, if that makes a little more sense.
oyster wrote:can anyone give some compile instruction?
Assuming you named the main module pdh.bas and the resource definition pdh.rc, a workable command line should be:

Code: Select all

fbc.exe -s gui pdh.rc pdh.bas
I don't use IDEs, so I can't advise you on anything related. To avoid conflicts between different programming tools I avoid using any specific environment settings. I just work from a subdirectory off the FreeBASIC directory and put the program-specific include files, resource definitions, and import libraries in the current directory. You might be able to avoid some confusion, if not problems, by assigning a different base name for the main module and resource definition. For example, HDDMon.bas and HDDMon.rc, to differentiate them from the PDH include file and import library.
oyster
Posts: 274
Joined: Oct 11, 2005 10:46

Post by oyster »

The problem is still here, and I don't think "-s gui" can lead to it.
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Post by MichaelW »

OK, I have been able to duplicate the problem. You are trying to compile the PDH include file, and you are getting error messages because the file was not intended to be compiled. It was meant to be included in the program source, after the include files that it depends on, and before any reference in the program source to the constants, data types, or procedure declarations that it contains.

The program source is in the first block of code that I posted. You should copy it to a file with a .bas extension, say “diskmon.bas”. The resource definition should be copied to a file with an .rc extension, say “diskmon.rc”. The PDH include file should be copied to a file named “pdh.bi”, as that is the name coded into the program source. Assuming you have correctly created and named the import library pdh.dll.a, and created the necessary icons, named as they are in the resource definition, you should be able to compile with:

“fbc /s gui diskmon.rc diskmon.bas”
oyster
Posts: 274
Joined: Oct 11, 2005 10:46

Post by oyster »

you are a nice man, MichaelW, thank you!
Sisophon2001
Posts: 1706
Joined: May 27, 2005 6:34
Location: Cambodia, Thailand, Lao, Ireland etc.
Contact:

Post by Sisophon2001 »

With delayed loading of DLL's it is difficult to time how slow your computer is booting. My office computer with Norton Anti-virus and a network connection appears to be unusable for the first five minutes after booting, while my home computer with NOD and dial-up is responsive once the windows desktop is visible.

Could this hard disk monitor be adapted to objectively compare boot times to full usability? When disk activity falls below some threshold, perhaps it could use one of the system counters to see how long windows had been running? I am not sure if it would work reliably.

Garvan
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Post by MichaelW »

Seems like a reasonable idea, but I have never checked to see what any version of this app does at startup. I think modifying it to log the activity levels to a file, say every 100ms, would be fairly painless.
TbbW
Posts: 348
Joined: Aug 19, 2005 10:08
Contact:

Post by TbbW »

voodooattack wrote:outstaning work! ^_^

5 thumbs up! ;D

i used some nice XP alpha icons, and it looks amazing :D

i can't stop watching that nifty lil icon (adds to startup) :)
so... no work done then? ;)

i had a proggy like that aswell ( i downloaded it but it whas for nw traffic monitoring. ) and i kept staring at it and got werry little work done coz of it.

so i had to uninstall it.
blahboybang
Posts: 385
Joined: Oct 16, 2005 0:15
Location: USA
Contact:

Post by blahboybang »

yarr! not again!

I made icons myself, geve them the correct names, and put them in the same directory.

But when I used fbc.exe -s gui ddam.rc ddam.bas...

I get error:

Error!
No resources found in RC file
OBJ file not made


WTF?!!

This has happened EVERY SINGLE TIME that I use an RC file! Yes, it's happened before, when no one else has had any problems. WTF am I doing wrong?

edit:
Tell you what. If someone will voulenteer, I will let them do remote desktop with me, do the command themselves, and see how f%cked up my computer is! Maybe then someone can help me fix this!

Msg me on msn: blahboybang@yahoo.com
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Post by MichaelW »

Try running GoRC directly, with a batch file something like this (alter as necessary):

Code: Select all

"c:\Program Files\freeBASIC0.16\bin\win32\gorc" diskmon.rc
pause
Post Reply