@echo off
rem Usage: LOOP DesiredLoopDuration 0 ssdSMARTlogger.bat
rem where the loop duration is specified in seconds
rem Must be run with Administrator privilege.
rem CHANGELOG: See bottom of this file.
if [%1]==[INIT] (
call :init
if errorlevel 1 EXIT /B 1
)
%SMARTCTL% -x %SSD%>%SNAP%
set Datestamp=!date:~4!
set Timestamp=!time: =!
set nAttribs=7
rem NOTE: Smartctl can't be relied on to output attributes on same lines
rem each time, so search lines for the 7 attribute id numbers.
for /F "skip=66 delims=" %%L IN (%SNAP%) do (
if !nAttribs! GTR 0 (
set line=%%L
set Attrib=[!line:~0,3!]
set AttID= 9
if !Attrib!==[!AttID!] (
set /A "PowerOnHours=!line:~61!, nAttribs-=1"
) else (
set AttID= 12
if !Attrib!==[!AttID!] (
set /A "PowerCycles=!line:~61!, nAttribs-=1"
) else (
set AttID=173
if !Attrib!==[!AttID!] (
set /A "ABEC=!line:~61!, nAttribs-=1"
) else (
set AttID=197
if !Attrib!==[!AttID!] (
rem Bogus_Current_Pend_Sect seems related to MX500 WAF bug, is 1 during FTL burst writes.
set /A "C5=!line:~61!, nAttribs-=1"
) else (
set AttID=246
if !Attrib!==[!AttID!] (
rem Skip 61 chars, then prepend blanks to ensure at least 12 chars,
rem and split into a high leading portion and the last 8 digits:
set line= !line:~61!
set /A "F6lo=1!line:~-8!-100000000, F6hi=!line:~0,-8!+0, nAttribs-=1"
) else (
set AttID=247
if !Attrib!==[!AttID!] (
set line= !line:~61!
set /A "F7lo=1!line:~-8!-100000000, F7hi=!line:~0,-8!+0, nAttribs-=1"
) else (
set AttID=248
if !Attrib!==[!AttID!] (
set line= !line:~61!
set /A "F8lo=1!line:~-8!-100000000, F8hi=!line:~0,-8!+0, nAttribs-=1"
) ) ) ) ) ) ) ) )
if !nAttribs! GTR 0 (
echo ERROR: !nAttribs! OF 7 SMART ATTRIBUTES NOT FOUND. Probably an unexpected ID#
echo Not all devices supply the same attributes, and smartctl plays a role too.
pause
)
rem Get an 8th attribute, Sectors Read, from the extended section of the "smartctl -x" output.
rem Note: Crucial MX500 ssd has firmware bug: Sectors Read rolls over at 2048 GB.
for /f "tokens=* delims=" %%L in ('findstr /C:"Logical Sectors Read" %SNAP%') do set "Smart=%%L"
set "Smart= !Smart:~15,15!"
set /A "Readlo=1!Smart:~-8!-100000000"
set /A "Readhi=!Smart:~0,-8!+0"
rem Concat leading_digits and end8digits_with_leading_zeros:
set zeroslo=00000000!F6lo!
set F6=!F6hi!!zeroslo:~-8!
set zeroslo=00000000!F7lo!
set F7=!F7hi!!zeroslo:~-8!
set zeroslo=00000000!F8lo!
set F8=!F8hi!!zeroslo:~-8!
set zeroslo=00000000!Readlo!
set SectorsRead=!Readhi!!zeroslo:~-8!
rem Calculate total host writes in GB rounded down to int, by dividing F6 by 2,097,152:
set /A "F6GB=F6hi*hiFactor+(F6hi*GB_mod+F6lo)/2097152"
rem Calculate total host reads in GB, taking Rollovers into account:
set /A ReadGB="Readhi*hiFactor+(Readhi*GB_mod+Readlo)/2097152+ReadRollovers*2048"
rem Each month, start a new log.
set month=!date:~4,2!
if NOT !month!==!prevmonth! (
set prevmonth=!month!
set Changed=Y
)
rem Change the log filename if month or params changed.
if !Changed!==Y (
set Changed=N
set datetime=!date:~10,4!.!date:~4,2!.!date:~7,2!-!time:~0,2!!time:~3,2!!time:~6,2!
set datetime=!datetime: =0!
set "LOG=%PROGDIR%\Logs\%BATNAME%%BATVER%_!datetime!_[%DESIREDSECONDS%_seconds].LOG"
echo Date,Time,TotalHostSectorsRd,TotalHostSectorsWr,TotalHostWrGB,TotalHostWrPages,TotalFTLPages,PowerOnHours,ABEC,PowerCycles, WAF, HostReadsMB,HostWritesMB,HostPages,FTLPages,C5 > !LOG!
)
echo %magenta%------------%yellow%
if !prevF6hi!!prevF6lo!==00 (
rem First pass. Display less data, and write header row to log files:
echo Total Host Sectors Read [!SectorsRead!]
echo Total Host LBAs Written [!F6!]
echo Total Host NAND Pages Written [!F7!]
echo Total FTL NAND Pages Written [!F8!]
echo !Datestamp!,!Timestamp!,!SectorsRead!,!F6!,!F6GB!,!F7!,!F8!,!PowerOnHours!,!ABEC!,!PowerCycles!,,,,,,!C5!>> !LOG!
) else (
rem Calculate changes in SMART counts F6,F7,F8,SectorsRead,etc:
set /A "deltaF6=100000000*(F6hi-prevF6hi)+F6lo-prevF6lo"
set /A "deltaF7=100000000*(F7hi-prevF7hi)+F7lo-prevF7lo"
set /A "deltaF8=100000000*(F8hi-prevF8hi)+F8lo-prevF8lo"
rem Convert Host_LBAs_Written to MBytes, with two decimal places:
rem Note that 1 LBA is 512 bytes.
set /A "_Int=deltaF6/2048, _Dec=((100*deltaF6)/2048)%%100"
rem Ensure decimal is two digits by prepending leading zeros and taking rightmost digits:
set _Dec=00!_Dec!
set dF6MB=!_Int!.!_Dec:~-2!
rem Note: ssd firmware bug causes Host Sectors Read rollover at 4,294,967,296 (32bit unsigned int)
if !Readhi! LSS !PrevReadhi! (
rem Rollover bug occurred, so compensate by substracting 4,294,967,296 from prev:
set /A "ReadRollovers+=1"
for /L %%H in (1,1,4) do (
rem Subtract 1,073,741,824 [one fourth of 4,294,967,296] from prev:
if !prevReadlo! GEQ 73741824 (
set /A "prevReadhi-=10, prevReadlo-=73741824"
) else (
set /A "prevReadhi-=11, prevReadlo+=(100000000-73741824)"
rem It's okay if prevReadhi goes negative.
)
)
set /A ReadGB+=2048"
)
rem Convert Host_Sectors_Read to MBytes, with two decimal places (1 sector = 512 bytes):
set /A "deltaRead=100000000*(Readhi-prevReadhi)+Readlo-prevReadlo"
set /A "_Int=deltaRead/2048, _Dec=((3*deltaRead)/64 + deltaRead/512)%%100"
rem Ensure decimal is two digits by prepending leading zeros and taking rightmost digits.
set _Dec=00!_Dec!
set dReadMB=!_Int!.!_Dec:~-2!
rem Calculate WAF with two decimal places, fudge if necessary to avoid divide by zero.
if !deltaF7! EQU 0 (
set "Waf=999999.99" && echo Avoided dividing by zero
) else (
set /A "WafInt=1+(deltaF8/deltaF7)"
set /A "WafDec=((100*deltaF8)/deltaF7)%%100"
rem Ensure decimal is two digits by prepending two leading zeros and taking rightmost two digits.
set WafDec=00!WafDec!
set WafDec=!WafDec:~-2!
set Waf=!WafInt!.!WafDec!
)
rem Display data (if bat file is not being run hidden).
echo WAF=!Waf! dHostRdMB=!dReadMB! dHostWrMB=!dF6MB! dHostPages=!deltaF7! dFTLPages=!deltaF8! C5=!C5!
echo TotalHostRdGB=!ReadGB! TotaldHostWrGB=!F6GB! TotalHostWrPages=!F7! TotalFTLPages=!F8!
echo POH=!PowerOnHours! ABEC=!ABEC! PowerCycles=!PowerCycles!
rem Append data to log file(s).
echo !Datestamp!,!Timestamp!,!SectorsRead!,!F6!,!F6GB!,!F7!,!F8!,!PowerOnHours!,!ABEC!,!PowerCycles!, !Waf!, !dReadMB!,!dF6MB!,!deltaF7!,!deltaF8!,!C5!>> !LOG!
if !WafInt! GTR 9 (
if !deltaF8! GEQ %LARGEFTL% (
if NOT exist %FTLLOG% (
echo Date,Time, WAF, HostReadsMB,HostWritesMB,HostPages, FTLPages, C5> %FTLLOG%
)
echo !Datestamp!,!Timestamp!, !Waf!, !dReadMB!,!dF6MB!,!deltaF7!, !deltaF8!, !C5!>> %FTLLOG%
) )
rem Check whether WAF for long period exceeded threshold, or if ABEC increased, and alert user if so.
set HighWAF=N
if %DESIREDSECONDS% GEQ 86400 (
rem Alert if daily WAF is at least 3.50:
if !WafInt! GEQ 3 (
if !WafInt! GEQ 4 (
set HighWAF=Y
) else (
if !WafDec! GEQ 50 (
set HighWAF=Y
) ) ) )
if !HighWAF!==Y (
set "_NotifyHeader=The ssd WAF was high."
set "_NotifyText=!Datestamp! !Timestamp! -- WAF was !WafInt!.!WafDec! during %DESIREDSECONDS% seconds period."
call :notify_using_PowerShell
call :logAlertData %WAFALERT%
)
if !ABEC! GTR !PrevABEC! (
set "_NotifyHeader=The ssd ABEC increased."
set "_NotifyText=!Datestamp! !Timestamp! -- Average Block Erase Count increased to !ABEC!."
call :notify_using_PowerShell
call :logABECIncrease
call :logAlertData %ABECALERT%
) )
set prevC5=!C5!
set prevABEC=!ABEC!
set prevF6hi=!F6hi!
set prevF6lo=!F6lo!
set prevF7hi=!F7hi!
set prevF7lo=!F7lo!
set prevF8hi=!F8hi!
set prevF8lo=!F8lo!
set prevReadhi=!Readhi!
set prevReadlo=!Readlo!
EXIT /B 0
:logAlertData
rem This subroutine is called if ABEC increased or if daily WAF is high.
if not exist %ALERTLOG% (
echo Date,Time,TotalHostSectorsRd,TotalHostSectorsWr,TotalHostWrGB,TotalHostWrPages,TotalFTLPages,PowerOnHours, ABEC, PowerCycles, WAF, HostReadsMB,HostWritesMB,HostPages,FTLPages,C5 > %ALERTLOG%
)
echo !Datestamp!,!Timestamp!, !ReadSectors!,!F6!,!F6GB!,!F7!,!F8!, !PowerOnHours!, !ABEC!, !PowerCycles!, !Waf!, !dReadMB!,!dF6MB!,!deltaF7!,!deltaF8!,!C5!>>%ALERTLOG%
rem Create a flag file and assume another .bat will detect the flag and display the data when convenient.
type NUL > "%~1"
EXIT /B 0
:notify_using_PowerShell
rem This subroutine displays a Windows balloon notification and adds it to Windows' Notification Center.
powershell -Command "&{[reflection.assembly]::loadwithpartialname('System.Windows.Forms'); " ^
"[reflection.assembly]::loadwithpartialname('System.Drawing'); " ^
"$notify = new-object system.windows.forms.notifyicon; " ^
"$notify.icon = [System.Drawing.SystemIcons]::Information; " ^
"$notify.visible = $true; " ^
"$notify.showballoontip(10,'%BATNAME%_v%BATVER% -- %_NotifyHeader%','%_NotifyText%',[system.windows.forms.tooltipicon]::None)} "
set "_NotifyHeader="
set "_NotifyText="
EXIT /B 0
:logABECIncrease
rem This subroutine is called if ABEC increased.
if not exist %ABECLOG% (
echo Date,Time,TotalHostSectorsRd,TotalHostSectorsWr,TotalHostWrGB,TotalHostWrPages,TotalFTLPages,PowerOnHours, ABEC, PowerCycles, WAF, HostReadsMB,HostWritesMB,HostPages,FTLPages,C5 > %ABECLOG%
)
echo !Datestamp!,!Timestamp!, !SectorsRead!,!F6!,!F6GB!,!F7!,!F8!, !PowerOnHours!, !ABEC!, !PowerCycles!, !Waf!, !dReadMB!,!dF6MB!,!deltaF7!,!deltaF8!,!C5!>>%ABECLOG%
EXIT /B 0
:init
set BATNAME=SSD_SMARTLogger
set BATVER=7.7.0
call %~dp0%setCONSTANTS.bat
TITLE %BATNAME%_v%BATVER% [Log SSD SMART data every %DESIREDSECONDS% seconds]
set "SNAP=%TMPDIR%\%BATNAME%%BATVER%_snap_%DESIREDSECONDS%.txt"
for /F "delims=#" %%E in ('"prompt #$E# & for %%E in (1) do rem"') do set "ESCchar=%%E"
set "green=%ESCchar%[92m"
set "yellow=%ESCchar%[93m"
set "magenta=%ESCchar%[95m"
set "cyan=%ESCchar%[96m"
set "white=%ESCchar%[97m"
set "resetcolor=%ESCchar%[0m"
if not exist "%SMARTCTL%" (
echo %magenta%Aborting: '%SMARTCTL%' not found.%white%
EXIT /B 1
)
rem Start a new log file each month.
set prevmonth=0
rem Embed the start date & time in the LargeFTL log filename and in the ABEC log filename:
set datetime=%date:~10,4%.%date:~4,2%.%date:~7,2%-%time:~0,2%%time:~3,2%%time:~6,2%
set datetime=%datetime: =0%
set "ABECLOG=%PROGDIR%\Logs\%BATNAME%%BATVER%_ABEC_%datetime%_[%DESIREDSECONDS%_second_intervals].LOG"
set "FTLLOG=%PROGDIR%\Logs\%BATNAME%%BATVER%_LargeFTL_%datetime%_[%DESIREDSECONDS%_second_intervals].LOG"
rem Note: Large FTL write bursts appear to be multiples of about 37000 pages, and
rem based on experience those do not occur while the ssd is running a selftest.
rem Write to the LargeFTL log only when FTL Page Writes is "relatively large":
if %DESIREDSECONDS% GEQ 86400 (
set LARGEFTL=100000
) else (
if %DESIREDSECONDS% GTR 10 (
set LARGEFTL=30000
) else (
rem There are occasional FTL write bursts of about 2400 pages that can
rem happen even while the ssd is running a selftest. Log those too.
set LARGEFTL=1000
) )
echo SMARTsnapshotfile [%SNAP%] AlertLog [%ALERTLOG%]
set prevF6hi=0
set prevF6lo=0
set prevF7hi=0
set prevF7lo=0
set prevF8hi=0
set prevF8lo=0
set prevC5=0
set prevABEC=0
set prevReadhi=0
set prevReadlo=0
rem Useful constants for converting sectors to GB:
set /A "GB_mod=100000000%%2097152, hiFactor=100000000/(1024*2048)"
set ReadRollovers=0
EXIT /B 0
========================================
CHANGELOG:
7.7.0 Use PowerShell to "balloon notify" user about high WAF and about increase of ABEC.
Use new version numbering system (3 numbers separated by periods).
7.6 Log data to new ABEC file each time ABEC increased if loop period is
less than one day, so that rows of long term data can be quickly
pasted to ABEC Increases spreadsheet sheet.
7.5 Moved definitions of constants to setCONSTANTS.bat
7.4 Removed version number from .bat filename.
Increased threshold for WAF Alert from 3 to 3.5.
Don't create the LargeFTL log file until a large FTL event happens.
Some code refactoring.
7.3 Log Large FTL only if WAF is also high, so LargeFTL log will be more meaningful.
TODO:
Track increases of Total NAND Pages Written (the key to Remaining Life) and alert user when high.
Add daily check for High WAF to shorter-than-daily logger so a single instance will suffice.
Save ReadRollovers and date in a file so that data won't be lost, and restore it from the file.