/* REXX */
/*--------------------------------------------------------------------*/
/* STORDIAG - z/OS storage first-response diagnostic                  */
/*                                                                    */
/* Author  : SARATH S                                                 */
/* Purpose : Read-only triage of real, auxiliary, common and virtual */
/*           storage, paging, and address-space storage consumers.    */
/*                                                                    */
/* Invocation examples:                                              */
/*   TSO STORDIAG                                                     */
/*   TSO STORDIAG 10                                                  */
/*   TSO STORDIAG 10 5 10                                            */
/*   TSO STORDIAG SAMPLE                                              */
/*   TSO STORDIAG HELP                                                */
/*                                                                    */
/* Parameters:                                                       */
/*   1 - Top rows in each ranking. Default 10.                        */
/*   2 - Number of SYS samples. Default 5.                            */
/*   3 - Seconds between SYS samples. Default 10.                     */
/*                                                                    */
/* Design principles:                                                */
/* - Single member. No site-specific data sets or job names.          */
/* - Read-only. No PAGEADD, cancel, dump or storage change is issued. */
/* - Uses SDSF SYS, AS ALL and VMAP.                                  */
/* - VMAP supplies layout/context; AS supplies virtual usage.         */
/* - Current-day SYSLOG scan is optional/best effort.                 */
/* - SAMPLE produces a teaching report without SDSF/system commands.  */
/* - High real-storage use alone is NOT called a shortage.            */
/* - Thresholds used here are triage heuristics, not IBM thresholds.  */
/* - Report remains cataloged after Browse as userid.STORDIAG.RPT.   */
/* - Under ISPF, results open in Browse for F7/F8/F9 support.         */
/*--------------------------------------------------------------------*/

numeric digits 20
signal on syntax name FatalSyntax

topN = 10
sampleCount = 5
intervalSec = 10

rpt. = ''
rpt.0 = 0
rptCount = 0
reportKind = 'RPT'
ispfActive = 0
sdsfOn = 0
ussOn = 0
asAvailable = 0
asLevel = 0
vmapAvailable = 0
vmapRows = 0
msgAvail = 0
msgCount = 0
msgStored = 0
msgCritical = 0

if sysvar('SYSISPF') = 'ACTIVE' then ispfActive = 1

localSys = translate(MVSVAR('SYSNAME'))

parse upper arg pTop pSamples pInterval .

if pTop = 'HELP' | pTop = 'H' | pTop = '?' then do
  reportKind = 'HELP'
  call HelpReport
  call PresentReport
  exit 0
end

if pTop = 'SAMPLE' then do
  if pSamples <> '' | pInterval <> '' then do
    call Emit 'STORDIAG: SAMPLE does not accept other parameters.'
    call PresentReport
    exit 12
  end

  reportKind = 'SAMPLE'
  call SampleReport
  call PresentReport
  exit 0
end

if pTop <> '' then topN = pTop
if pSamples <> '' then sampleCount = pSamples
if pInterval <> '' then intervalSec = pInterval

if datatype(topN,'W') = 0 then do
  call Emit 'STORDIAG: Top-row value must be a whole number.'
  call PresentReport
  exit 12
end

if datatype(sampleCount,'W') = 0 then do
  call Emit 'STORDIAG: Sample count must be a whole number.'
  call PresentReport
  exit 12
end

if datatype(intervalSec,'W') = 0 then do
  call Emit 'STORDIAG: Sample interval must be a whole number.'
  call PresentReport
  exit 12
end

if topN < 1 then topN = 10
if topN > 25 then topN = 25

if sampleCount < 3 then sampleCount = 3
if sampleCount > 12 then sampleCount = 12

if intervalSec < 5 then intervalSec = 5
if intervalSec > 60 then intervalSec = 60

sampleTime. = ''
sampleOK. = 0
sampleRealPct. = 0
sampleAvail. = 0
sampleAvailPct. = 0
sampleAux. = 0
samplePage. = 0
sampleCSA. = 0
sampleECSA. = 0
sampleSQA. = 0
sampleESQA. = 0
sampleUIC. = ''

validSamples = 0
sumRealPct = 0
sumAvailPct = 0
sumAux = 0
sumPage = 0
peakRealPct = 0
peakAux = 0
peakPage = 0
peakCSA = 0
peakECSA = 0
peakSQA = 0
peakESQA = 0
minAvail = -1
firstAvail = 0
lastAvail = 0

lastSYSLEVEL = 'N/A'
lastREAL = 'N/A'
lastREALAFC = 'N/A'
lastREALAFCB = 'N/A'
lastAUXPCT = 'N/A'
lastPAGERATE = 'N/A'
lastCSAPCT = 'N/A'
lastECSAPCT = 'N/A'
lastSQAPCT = 'N/A'
lastESQAPCT = 'N/A'
lastUIC = 'N/A'
lastFIXPCT = 'N/A'
lastFIXBPCT = 'N/A'
lastSTCNUM = 'N/A'
lastJOBNUM = 'N/A'
lastTSUNUM = 'N/A'
lastIPLDATE = 'N/A'
lastIPLDAYS = 'N/A'
lastSTATDATE = 'N/A'

asName. = ''
asJobid. = ''
asType. = ''
asAsid. = ''
asReal. = 0
asFixed. = 0
asAux. = 0
asCSA. = 0
asECSA. = 0
asSQA. = 0
asESQA. = 0
asPrivPct. = ''
asEPrivPct. = ''
asPrivNum. = 0
asEPrivNum. = 0
asMObj. = 0
asMObjHWM. = 0
asMemLimit. = ''
asRows = 0

vmapName. = ''
vmapStart. = ''
vmapEnd. = ''
vmapSize. = ''
vmapSys. = ''

peakPriv = 0
peakEPriv = 0
peakPrivJob = 'N/A'
peakEPrivJob = 'N/A'

msgLine. = ''

rc = isfcalls('ON')
if rc <> 0 then do
  call Emit 'STORDIAG: Unable to establish SDSF REXX environment.'
  call Emit 'RC='rc
  call PresentReport
  exit 20
end
sdsfOn = 1

isfprefix = '**'
isfowner = '*'
isfsysname = localSys

call FetchSYS 0
if result <> 0 then do
  call Emit 'STORDIAG: SDSF SYS data could not be obtained.'
  call Cleanup
  call PresentReport
  exit 20
end

ussRc = syscalls('ON')
if ussRc = 7 | ussRc = 8 then do
  call Emit 'STORDIAG: Timed sampling could not be initialized.'
  call Emit 'SYSCALLS RC='ussRc
  call Cleanup
  call PresentReport
  exit 20
end
ussOn = 1

do s = 1 to sampleCount
  sampleTime.s = time('N')

  call FetchSYS s
  if result = 0 then do
    sampleOK.s = 1
    validSamples = validSamples + 1

    rPct = sampleRealPct.s
    aPct = sampleAux.s
    pRate = samplePage.s
    avPct = sampleAvailPct.s

    sumRealPct = sumRealPct + rPct
    sumAvailPct = sumAvailPct + avPct
    sumAux = sumAux + aPct
    sumPage = sumPage + pRate

    if rPct > peakRealPct then peakRealPct = rPct
    if aPct > peakAux then peakAux = aPct
    if pRate > peakPage then peakPage = pRate

    if sampleCSA.s > peakCSA then peakCSA = sampleCSA.s
    if sampleECSA.s > peakECSA then peakECSA = sampleECSA.s
    if sampleSQA.s > peakSQA then peakSQA = sampleSQA.s
    if sampleESQA.s > peakESQA then peakESQA = sampleESQA.s

    af = sampleAvail.s
    if minAvail < 0 | af < minAvail then minAvail = af
    if firstAvail = 0 then firstAvail = af
    lastAvail = af
  end

  if s < sampleCount then call SleepSeconds intervalSec
end

if validSamples = 0 then do
  call Emit 'STORDIAG: No valid storage samples were collected.'
  call Cleanup
  call PresentReport
  exit 20
end

avgRealPct = sumRealPct / validSamples
avgAvailPct = sumAvailPct / validSamples
avgAux = sumAux / validSamples
avgPage = sumPage / validSamples

availDropPct = 0
if firstAvail > 0 & lastAvail < firstAvail then
  availDropPct = (firstAvail - lastAvail) * 100 / firstAvail

call FetchAS
call FetchVMAP
call DetectStorageMessages
call Evaluate

call Header
call TriageSummary
call SampleHistory
call SystemContext
call VirtualStorageSummary
call VmapReport

if asAvailable then do
  call PrintTopSimple 'REAL', 'TOP REAL-STORAGE CONSUMERS'
  call PrintTopSimple 'AUX', 'TOP AUXILIARY-STORAGE CONSUMERS'

  if asLevel >= 2 then do
    call PrintTopSimple 'FIXED', 'TOP FIXED-FRAME CONSUMERS'
    call PrintTopCommon
  end

  if asLevel = 3 then do
    call PrintTopPrivate
    call PrintTopSimple 'MOBJ', 'TOP MEMORY-OBJECT CONSUMERS'
  end
end
else do
  call Section 'ADDRESS-SPACE STORAGE CONSUMERS'
  call Emit 'SDSF AS ALL data was unavailable for this invocation.'
end

call MessageReport
call Analysis
call Recommendations
call CommandReference
call Notes
call Footer
call Cleanup
call PresentReport
exit 0


/*====================================================================*/
/* SDSF DATA COLLECTION                                               */
/*====================================================================*/

FetchSYS:
parse arg fsSample

drop SYSNAME. SYSLEVEL. AUXPCT. CSAPCT. ECSAPCT.
drop SQAPCT. ESQAPCT. UIC. PAGERATE. REAL. REALAFC.
drop REALAFCB. FIXPCT. FIXBPCT. STCNUM. JOBNUM. TSUNUM.
drop IPLDATE. IPLDAYS. STATDATE.

isfcols = 'SYSNAME SYSLEVEL AUXPCT CSAPCT ECSAPCT SQAPCT' ,
          'ESQAPCT UIC PAGERATE REAL REALAFC REALAFCB FIXPCT' ,
          'FIXBPCT STCNUM JOBNUM TSUNUM IPLDATE IPLDAYS STATDATE'

Address SDSF "ISFEXEC SYS"
fsRc = rc
sysRich = 1

if fsRc <> 0 then do
  drop SYSNAME. SYSLEVEL. AUXPCT. CSAPCT. ECSAPCT.
  drop SQAPCT. ESQAPCT. UIC. PAGERATE. REAL. REALAFC.
  drop STCNUM. JOBNUM. TSUNUM. IPLDATE. IPLDAYS. STATDATE.

  isfcols = 'SYSNAME SYSLEVEL AUXPCT CSAPCT ECSAPCT SQAPCT' ,
            'ESQAPCT UIC PAGERATE REAL REALAFC STCNUM JOBNUM' ,
            'TSUNUM IPLDATE IPLDAYS STATDATE'

  Address SDSF "ISFEXEC SYS"
  fsRc = rc
  sysRich = 0
end

if fsRc <> 0 then do
  call Emit 'STORDIAG: SDSF SYS access failed. RC='fsRc
  call SdsfMessages
  return fsRc
end

rows = isfrows
if rows = 0 then return 8

pick = 0
do i = 1 to rows
  if translate(strip(SYSNAME.i)) = localSys then do
    pick = i
    leave
  end
end

if pick = 0 then pick = 1

lastSYSLEVEL = ValueOr(SYSLEVEL.pick,'N/A')
lastAUXPCT = ValueOr(AUXPCT.pick,'N/A')
lastCSAPCT = ValueOr(CSAPCT.pick,'N/A')
lastECSAPCT = ValueOr(ECSAPCT.pick,'N/A')
lastSQAPCT = ValueOr(SQAPCT.pick,'N/A')
lastESQAPCT = ValueOr(ESQAPCT.pick,'N/A')
lastUIC = ValueOr(UIC.pick,'N/A')
lastPAGERATE = ValueOr(PAGERATE.pick,'N/A')
lastREAL = ValueOr(REAL.pick,'N/A')
lastREALAFC = ValueOr(REALAFC.pick,'N/A')
lastSTCNUM = ValueOr(STCNUM.pick,'N/A')
lastJOBNUM = ValueOr(JOBNUM.pick,'N/A')
lastTSUNUM = ValueOr(TSUNUM.pick,'N/A')
lastIPLDATE = ValueOr(IPLDATE.pick,'N/A')
lastIPLDAYS = ValueOr(IPLDAYS.pick,'N/A')
lastSTATDATE = ValueOr(STATDATE.pick,'N/A')

if sysRich then do
  lastREALAFCB = ValueOr(REALAFCB.pick,'N/A')
  lastFIXPCT = ValueOr(FIXPCT.pick,'N/A')
  lastFIXBPCT = ValueOr(FIXBPCT.pick,'N/A')
end

if fsSample > 0 then do
  totalFrames = Num(REAL.pick)
  availFrames = Num(REALAFC.pick)

  realPct = 0
  availPct = 0

  if totalFrames > 0 then do
    realPct = (totalFrames - availFrames) * 100 / totalFrames
    availPct = availFrames * 100 / totalFrames
  end

  sampleRealPct.fsSample = realPct
  sampleAvail.fsSample = availFrames
  sampleAvailPct.fsSample = availPct
  sampleAux.fsSample = Num(AUXPCT.pick)
  samplePage.fsSample = Num(PAGERATE.pick)
  sampleCSA.fsSample = Num(CSAPCT.pick)
  sampleECSA.fsSample = Num(ECSAPCT.pick)
  sampleSQA.fsSample = Num(SQAPCT.pick)
  sampleESQA.fsSample = Num(ESQAPCT.pick)
  sampleUIC.fsSample = ValueOr(UIC.pick,'N/A')
end

return 0


FetchAS:
drop JNAME. JOBID. JTYPE. ASID. SYSNAME.
drop REAL. FIXED. AUX. CSA. ECSA. SQA. ESQA.
drop PRIVPCT. EPRIVPCT. MOBJ. MOBJHWM. MEMLIMIT.

isfcols = 'JNAME JOBID JTYPE ASID SYSNAME REAL FIXED AUX CSA' ,
          'ECSA SQA ESQA PRIVPCT EPRIVPCT MOBJ MOBJHWM MEMLIMIT'

Address SDSF "ISFEXEC AS ALL"
asRc = rc

if asRc = 0 then asLevel = 3
else do
  drop JNAME. JOBID. JTYPE. ASID. SYSNAME.
  drop REAL. FIXED. AUX. CSA. ECSA. SQA. ESQA.

  isfcols = 'JNAME JOBID JTYPE ASID SYSNAME REAL FIXED AUX' ,
            'CSA ECSA SQA ESQA'

  Address SDSF "ISFEXEC AS ALL"
  asRc = rc

  if asRc = 0 then asLevel = 2
  else do
    drop JNAME. JOBID. JTYPE. ASID. SYSNAME. REAL. AUX.

    isfcols = 'JNAME JOBID JTYPE ASID SYSNAME REAL AUX'
    Address SDSF "ISFEXEC AS ALL"
    asRc = rc

    if asRc = 0 then asLevel = 1
  end
end

if asRc <> 0 then do
  asAvailable = 0
  return
end

asAvailable = 1
asRows = isfrows

do i = 1 to asRows
  asName.i = ValueOr(JNAME.i,'(BLANK)')
  asJobid.i = ValueOr(JOBID.i,'-')
  asType.i = ValueOr(JTYPE.i,'-')
  asAsid.i = ValueOr(ASID.i,'-')
  asReal.i = ScaledNum(REAL.i)
  asAux.i = ScaledNum(AUX.i)

  if asLevel >= 2 then do
    asFixed.i = ScaledNum(FIXED.i)
    asCSA.i = ScaledNum(CSA.i)
    asECSA.i = ScaledNum(ECSA.i)
    asSQA.i = ScaledNum(SQA.i)
    asESQA.i = ScaledNum(ESQA.i)
  end

  if asLevel = 3 then do
    asPrivPct.i = ValueOr(PRIVPCT.i,'N/A')
    asEPrivPct.i = ValueOr(EPRIVPCT.i,'N/A')
    asPrivNum.i = Num(PRIVPCT.i)
    asEPrivNum.i = Num(EPRIVPCT.i)
    asMObj.i = ScaledNum(MOBJ.i)
    asMObjHWM.i = ScaledNum(MOBJHWM.i)
    asMemLimit.i = ValueOr(MEMLIMIT.i,'N/A')
  end
end
return


FetchVMAP:
drop NAME. START. END. SIZE. SYSNAME. SYSLEVEL.

isfcols = 'NAME START END SIZE SYSNAME SYSLEVEL'
Address SDSF "ISFEXEC VMAP"
vmRc = rc

if vmRc <> 0 then do
  vmapAvailable = 0
  vmapRows = 0
  return
end

vmapAvailable = 1
vmapRows = isfrows

do i = 1 to vmapRows
  vmapName.i = ValueOr(NAME.i,'N/A')
  vmapStart.i = ValueOr(START.i,'N/A')
  vmapEnd.i = ValueOr(END.i,'N/A')
  vmapSize.i = ValueOr(SIZE.i,'N/A')
  vmapSys.i = ValueOr(SYSNAME.i,localSys)
end
return


DetectStorageMessages:
drop isfstartlinetoken
drop isfnextlinetoken
drop isfline.

isffind = 'IRA'
isffindlim = 9999999
isfscrolltype = 'FINDNEXT'
isflinelim = 1

logLoops = 0

do forever
  Address SDSF "ISFLOG READ TYPE(SYSLOG)"
  logRc = rc

  if logRc <> 0 then do
    msgAvail = 0
    leave
  end

  msgAvail = 1
  logLoops = logLoops + 1

  if logLoops > 5000 then leave

  if isfline.0 > 0 then do
    ln = strip(isfline.1)
    mid = StorageMsgId(translate(ln))

    if mid <> '' then do
      msgCount = msgCount + 1

      if msgStored < 20 then do
        msgStored = msgStored + 1
        msgLine.msgStored = ln
      end

      if mid = 'IRA101E' | mid = 'IRA201E' | ,
         mid = 'IRA220I' | mid = 'IRA401E' then
        msgCritical = 1
    end
  end

  if symbol('ISFNEXTLINETOKEN') = 'LIT' then leave
  if isfnextlinetoken = '' then leave

  isfstartlinetoken = isfnextlinetoken
end

drop isffind isffindlim isfscrolltype isflinelim
drop isfstartlinetoken isfnextlinetoken
return


/*====================================================================*/
/* EVALUATION                                                         */
/*====================================================================*/

Evaluate:
peakPriv = 0
peakEPriv = 0
peakPrivJob = 'N/A'
peakEPrivJob = 'N/A'

if asAvailable & asLevel = 3 then do
  do i = 1 to asRows
    if asPrivNum.i > peakPriv then do
      peakPriv = asPrivNum.i
      peakPrivJob = asName.i
    end

    if asEPrivNum.i > peakEPriv then do
      peakEPriv = asEPrivNum.i
      peakEPrivJob = asName.i
    end
  end
end

commonPeak = peakCSA
highCommonName = 'CSA'

if peakECSA > commonPeak then do
  commonPeak = peakECSA
  highCommonName = 'ECSA'
end

if peakSQA > commonPeak then do
  commonPeak = peakSQA
  highCommonName = 'SQA'
end

if peakESQA > commonPeak then do
  commonPeak = peakESQA
  highCommonName = 'ESQA'
end

severity = 'LOW'
condition = 'NO OBVIOUS CURRENT STORAGE SHORTAGE'

if peakAux >= 95 & commonPeak >= 95 then do
  severity = 'HIGH'
  condition = 'MULTIPLE STORAGE PRESSURES'
end
else if peakAux >= 95 then do
  severity = 'HIGH'
  condition = 'AUXILIARY-STORAGE PRESSURE'
end
else if commonPeak >= 95 then do
  severity = 'HIGH'
  condition = 'COMMON-STORAGE PRESSURE'
end
else if peakAux >= 90 then do
  severity = 'MEDIUM'
  condition = 'ELEVATED AUXILIARY-STORAGE USE'
end
else if commonPeak >= 90 then do
  severity = 'MEDIUM'
  condition = 'ELEVATED COMMON-STORAGE USE'
end
else if availDropPct >= 20 & peakPage > 0 then do
  severity = 'MEDIUM'
  condition = 'REAL-FRAME DECLINE WITH PAGING'
end
else if peakPriv >= 95 | peakEPriv >= 95 then do
  severity = 'MEDIUM'
  condition = 'ADDRESS-SPACE VIRTUAL-STORAGE PRESSURE'
end

if peakPage = 0 then
  pagingState = 'NO PAGING OBSERVED IN SAMPLE WINDOW'
else
  pagingState = 'PAGING OBSERVED - CORRELATE WITH OTHER PRESSURE'

if availDropPct >= 20 & peakPage > 0 then
  realState = 'AVAILABLE FRAMES FELL WHILE PAGING WAS ACTIVE'
else
  realState = 'REAL USE IS CONTEXT; HIGH USE ALONE IS NOT SHORTAGE'

if msgAvail = 0 then
  msgState = 'SYSLOG UNAVAILABLE / NOT AUTHORIZED'
else if msgCount = 0 then
  msgState = 'NO MATCHING CURRENT-DAY IRA STORAGE MESSAGES'
else if msgCritical then
  msgState = 'CRITICAL CURRENT-DAY IRA MESSAGE SEEN'
else
  msgState = 'CURRENT-DAY IRA STORAGE MESSAGE(S) SEEN'
return


/*====================================================================*/
/* SAMPLE REPORT                                                      */
/*====================================================================*/

SampleReport:
call Emit ''
call Emit copies('=',78)
call Emit ' STORDIAG - STORAGE FIRST-RESPONSE DIAGNOSTIC'
call KV 'Author', 'SARATH S'
call KV 'Date', DisplayDate()
call KV 'Time', time('N')
call KV 'System', localSys
call KV 'Report type', 'SAMPLE - NO LIVE STORAGE DATA COLLECTED'
call Emit copies('=',78)

call Section 'SAMPLE REPORT NOTICE'
call Emit 'This report demonstrates STORDIAG output and interpretation.'
call Emit 'Except for System, Date, Time and documented commands, the'
call Emit 'values, job names, messages, addresses and data sets below'
call Emit 'are SAMPLE DATA. They do not describe the current LPAR.'
call Emit ''
call Emit 'SAMPLE mode does not read SDSF, SYSLOG or issue system commands.'

call Section 'TRIAGE SUMMARY'
call KV 'Severity', 'HIGH (SAMPLE)'
call KV 'Condition', 'CRITICAL AUXILIARY-STORAGE PRESSURE'
call KV 'Average real storage used', '91.8%'
call KV 'Peak real storage used', '93.4%'
call KV 'Average real frames available', '8.2%'
call KV 'Available-frame decline', '18.5%'
call KV 'Average auxiliary used', '88.8%'
call KV 'Peak auxiliary used', '96.2%'
call KV 'Average paging rate', '124.6'
call KV 'Peak paging rate', '182.0'
call KV 'Highest common-storage area', 'ECSA'
call KV 'Peak common-storage use', '72.4%'
call KV 'Highest private below 16M', '86.4%  SAMPSTC1'
call KV 'Highest private above 16M', '97.3%  SAMPJAVA'
call KV 'Real-storage assessment', 'HIGH USE; NOT ROOT CAUSE BY ITSELF'
call KV 'Paging assessment', 'ACTIVE WITH AUX PRESSURE'
call KV 'Message evidence', 'IRA201E SEEN (SAMPLE)'

call Section 'STORAGE SAMPLE HISTORY'
call Emit 'Smp Time      Real% Avail% Aux%  PageRate CSA% ECSA% SQA% ESQA%'
call Emit copies('-',72)
call Emit '  1 14:10:00  89.8    10.2  78.4     42.0 41.2  61.5 35.0  43.4'
call Emit '  2 14:10:10  90.7     9.3  84.6     76.0 42.0  64.8 36.2  44.7'
call Emit '  3 14:10:20  91.9     8.1  90.1    139.0 43.1  68.2 38.1  47.5'
call Emit '  4 14:10:30  93.1     6.9  94.7    184.0 44.2  70.6 40.4  49.8'
call Emit '  5 14:10:40  93.4     6.6  96.2    182.0 45.0  72.4 41.1  51.3'

call Section 'SYSTEM STORAGE STATUS'
call KV 'z/OS level', '03.01 (SAMPLE)'
call KV 'Real frames online', '8388608 (SAMPLE)'
call KV 'Real frames available', '553648 (SAMPLE)'
call KV 'Real frames avail below 16M', '18420 (SAMPLE)'
call KV 'Fixed real frames', '21.4% (SAMPLE)'
call KV 'Fixed frames below 16M', '17.8% (SAMPLE)'
call KV 'Auxiliary storage used', '96.2% (SAMPLE)'
call KV 'Paging rate', '182.0 (SAMPLE)'
call KV 'UIC', '1120 (SAMPLE)'
call KV 'CSA used', '45.0% (SAMPLE)'
call KV 'ECSA used', '72.4% (SAMPLE)'
call KV 'SQA used', '41.1% (SAMPLE)'
call KV 'ESQA used', '51.3% (SAMPLE)'

call Section 'VIRTUAL STORAGE STATUS'
call KV 'Highest Priv% below 16M', '86.4%  SAMPSTC1'
call KV 'Highest EPriv% above 16M', '97.3%  SAMPJAVA'
call Emit ''
call Emit 'Interpretation: SAMPJAVA is close to its sampled extended'
call Emit 'private-storage limit. This is an address-space virtual'
call Emit 'storage warning and is separate from real-storage use.'
call Emit 'SAMPSTC1 below-line use is elevated but below the sample'
call Emit 'critical threshold.'

call Section 'SYSTEM VIRTUAL STORAGE MAP (SDSF VMAP)'
call Emit 'Area             Start-Address     End-Address       Size'
call Emit copies('-',66)
call Emit 'CSA              00000000          00FFFFFF          16M'
call Emit 'SQA              01000000          01FFFFFF          16M'
call Emit 'PRIVATE          02000000          7FFFFFFF        2016M'
call Emit 'EXTENDED         80000000          FFFFFFFF        2048M'
call Emit ''
call Emit 'SAMPLE ADDRESSES ONLY. VMAP is layout evidence, not usage %.'

call Section 'TOP REAL-STORAGE CONSUMERS'
call Emit 'Rank Jobname  Type ASID  Metric                 FRAMES'
call Emit copies('-',66)
call Emit '   1 SAMPDB2  STC   003A              925000'
call Emit '   2 SAMPJAVA STC   0041              811000'
call Emit '   3 SAMPJOB1 JOB   007C              442000'

call Section 'TOP AUXILIARY-STORAGE CONSUMERS'
call Emit 'Rank Jobname  Type ASID  Metric                 SLOTS'
call Emit copies('-',66)
call Emit '   1 SAMPJAVA STC   0041             2100000'
call Emit '   2 SAMPDB2  STC   003A             1460000'
call Emit '   3 SAMPSTC1 STC   0055              780000'

call Section 'TOP FIXED-FRAME CONSUMERS'
call Emit 'Rank Jobname  Type ASID  Metric                 FRAMES'
call Emit copies('-',66)
call Emit '   1 SAMPDB2  STC   003A              118000'
call Emit '   2 SAMPJAVA STC   0041               69000'
call Emit '   3 SAMPSTC1 STC   0055               41000'

call Section 'TOP COMMON-STORAGE CONSUMERS'
call Emit 'Rank Jobname  ASID     CSA     ECSA      SQA     ESQA    Total'
call Emit copies('-',72)
call Emit '   1 SAMPSTC1 0055    2.1M    48.0M     1.0M     5.2M    56.3M'
call Emit '   2 SAMPDB2  003A    1.5M    22.4M     0.6M     2.9M    27.4M'
call Emit '   3 SAMPJAVA 0041    0.8M    11.2M     0.3M     1.7M    14.0M'

call Section 'TOP PRIVATE VIRTUAL-STORAGE CONSUMERS'
call Emit 'Rank Jobname  Type ASID   Priv%  EPriv%  MemObjMB  MemLimit'
call Emit copies('-',72)
call Emit '   1 SAMPJAVA STC   0041    18.2    97.3      6144        8G'
call Emit '   2 SAMPSTC1 STC   0055    86.4    42.2       512        2G'
call Emit '   3 SAMPDB2  STC   003A    22.7    68.1      4096        8G'

call Section 'MESSAGE EVIDENCE'
call KV 'Matching messages found', '2 (SAMPLE)'
call Emit ' 1. IRA200E AUXILIARY STORAGE SHORTAGE (SAMPLE)'
call Emit ' 2. IRA201E CRITICAL AUXILIARY STORAGE SHORTAGE (SAMPLE)'

call Section 'ANALYSIS / INTERPRETATION'
call Emit '1. The sample represents a critical AUX shortage.'
call Emit '   Aux usage rose to 96.2% while paging also increased.'
call Emit '2. SAMPJAVA is both the largest sample AUX consumer and has'
call Emit '   97.3% EPriv. It is a strong investigation candidate, not'
call Emit '   proof that the address space caused the system condition.'
call Emit '3. ECSA is elevated but not the primary sample shortage.'
call Emit '4. Real storage is highly used, but high Real% alone is not'
call Emit '   treated as proof of a real-storage shortage.'

call Section 'RECOMMENDED DEBUGGING / POSSIBLE RESOLUTION'
call Emit '1. Issue D ASM and verify actual page-data-set/SCM status.'
call Emit '2. Review the top AUX consumers and confirm whether growth is'
call Emit '   expected before canceling or recycling any address space.'
call Emit '3. For high Priv/EPriv, use SDSF JM and review REGION, IEFUSI'
call Emit '   policy and application-specific storage growth.'
call Emit '4. If AUX shortage is confirmed and an approved formatted'
call Emit '   page data set exists, PAGEADD can provide temporary relief.'
call Emit '5. Preserve diagnostic evidence before recycling a suspected'
call Emit '   storage-leaking address space when site procedure requires.'
call Emit '6. Correct the root cause; extra paging space alone is not a'
call Emit '   permanent fix for uncontrolled storage growth.'

call Section 'COMMAND REFERENCE'
call Emit 'DISPLAY / READ-ONLY COMMANDS'
call Emit '  D ASM'
call Emit '  D ASM,LOCAL'
call Emit '  D VIRTSTOR'
call Emit '  D M=STOR'
call Emit ''
call Emit 'SDSF PANELS'
call Emit '  SYS   AS   VMAP   JM'
call Emit ''
call Emit 'RECOVERY / CHANGE COMMAND - MANUAL REVIEW REQUIRED'
call Emit '  PAGEADD PAGE=<approved.page.data.set>'
call Emit ''
call Emit 'No generic MODIFY/F command is shown for private storage.'
call Emit 'Application-specific F commands must come from the product'
call Emit 'procedure/runbook. STORDIAG itself issues none of these.'

call Section 'IMPORTANT NOTES'
call Emit '  * This is SAMPLE output, not current system evidence.'
call Emit '  * Only System, Date, Time and command syntax are live/real.'
call Emit '  * Sample job names begin with SAMP to avoid confusion.'
call Emit '  * Sample VMAP addresses and resource values are illustrative.'
call Emit '  * 90%/95% levels are STORDIAG triage heuristics.'
call Emit '  * Real-storage use alone never drives a HIGH condition.'
call Emit '  * SAMPLE mode performs no SDSF or console-command activity.'

call Emit ''
call Emit copies('=',78)
call Emit ' END OF STORDIAG SAMPLE REPORT - NO LIVE DIAGNOSIS WAS PERFORMED'
call Emit copies('=',78)
return


/*====================================================================*/
/* REPORT                                                             */
/*====================================================================*/

Header:
call Emit ''
call Emit copies('=',78)
call Emit ' STORDIAG - STORAGE FIRST-RESPONSE DIAGNOSTIC'
call KV 'Author', 'SARATH S'
call KV 'Date', DisplayDate()
call KV 'Time', time('N')
call KV 'System', localSys
call Emit copies('=',78)
return


TriageSummary:
call Section 'TRIAGE SUMMARY'
call KV 'Current severity', severity
call KV 'Current condition', condition
call KV 'Average real storage used', FormatPct(avgRealPct)
call KV 'Peak real storage used', FormatPct(peakRealPct)
call KV 'Average real frames available', FormatPct(avgAvailPct)
call KV 'Available-frame decline', FormatPct(availDropPct)
call KV 'Average auxiliary used', FormatPct(avgAux)
call KV 'Peak auxiliary used', FormatPct(peakAux)
call KV 'Average paging rate', FormatNum(avgPage,2)
call KV 'Peak paging rate', FormatNum(peakPage,2)
call KV 'Highest common-storage area', highCommonName
call KV 'Peak common-storage use', FormatPct(commonPeak)

if asAvailable & asLevel = 3 then do
  call KV 'Highest private below 16M', ,
          FormatPct(peakPriv)'  'peakPrivJob
  call KV 'Highest private above 16M', ,
          FormatPct(peakEPriv)'  'peakEPrivJob
end
else do
  call KV 'Private virtual-storage data', 'UNAVAILABLE'
end

call KV 'Real-storage assessment', realState
call KV 'Paging assessment', pagingState
call KV 'Current-day message evidence', msgState
return


SampleHistory:
call Section 'STORAGE SAMPLE HISTORY'
call Emit 'Smp Time      Real% Avail% Aux%  PageRate CSA% ECSA% SQA% ESQA%'
call Emit copies('-',72)

do i = 1 to sampleCount
  if sampleOK.i then do
    line = right(i,3) ,
           left(sampleTime.i,9) ,
           right(ShortPct(sampleRealPct.i),5) ,
           right(ShortPct(sampleAvailPct.i),6) ,
           right(ShortPct(sampleAux.i),5) ,
           right(FormatNum(samplePage.i,1),8) ,
           right(ShortPct(sampleCSA.i),4) ,
           right(ShortPct(sampleECSA.i),5) ,
           right(ShortPct(sampleSQA.i),4) ,
           right(ShortPct(sampleESQA.i),5)
  end
  else do
    line = right(i,3) left(sampleTime.i,9) ,
           right('N/A',5) right('N/A',6) right('N/A',5) ,
           right('N/A',8) right('N/A',4) right('N/A',5) ,
           right('N/A',4) right('N/A',5)
  end

  call Emit line
end
return


SystemContext:
call Section 'SYSTEM STORAGE STATUS'
call KV 'z/OS level', lastSYSLEVEL
call KV 'Real frames online', lastREAL
call KV 'Real frames available', lastREALAFC
call KV 'Real frames avail below 16M', lastREALAFCB
call KV 'Fixed real frames', AddPct(lastFIXPCT)
call KV 'Fixed frames below 16M', AddPct(lastFIXBPCT)
call KV 'Auxiliary storage used', AddPct(lastAUXPCT)
call KV 'Paging rate', lastPAGERATE
call KV 'UIC', lastUIC
call KV 'CSA used', AddPct(lastCSAPCT)
call KV 'ECSA used', AddPct(lastECSAPCT)
call KV 'SQA used', AddPct(lastSQAPCT)
call KV 'ESQA used', AddPct(lastESQAPCT)
call KV 'Active started tasks', lastSTCNUM
call KV 'Active batch jobs', lastJOBNUM
call KV 'Active TSO users', lastTSUNUM
call KV 'IPL date/time', lastIPLDATE
call KV 'Days since IPL', lastIPLDAYS
call KV 'SYS statistics time', lastSTATDATE
return


VirtualStorageSummary:
call Section 'VIRTUAL STORAGE STATUS'

if \asAvailable | asLevel <> 3 then do
  call Emit 'Private virtual-storage percentages were not available.'
  call Emit 'The remaining storage analysis is still valid.'
  return
end

call KV 'Highest Priv% below 16M', ,
        FormatPct(peakPriv)'  'peakPrivJob
call KV 'Highest EPriv% above 16M', ,
        FormatPct(peakEPriv)'  'peakEPrivJob

call Emit ''
call Emit 'Priv% is private storage used below the 16 MB line.'
call Emit 'EPriv% is private storage used above the 16 MB line.'
call Emit 'A high value is an address-space virtual-storage warning;'
call Emit 'it is different from real-storage or AUX pressure.'

if peakPriv >= 95 then do
  call Emit ''
  call Emit 'Below-line private storage is very high for' peakPrivJob'.'
  call Emit 'Check the address space for below-line storage growth.'
end
else if peakPriv >= 90 then do
  call Emit ''
  call Emit 'Below-line private storage is elevated for' peakPrivJob'.'
end

if peakEPriv >= 95 then do
  call Emit ''
  call Emit 'Above-line private storage is very high for' peakEPrivJob'.'
  call Emit 'Inspect that address space before it exhausts its region.'
end
else if peakEPriv >= 90 then do
  call Emit ''
  call Emit 'Above-line private storage is elevated for' peakEPrivJob'.'
end

call Emit ''
call Emit 'VMAP is used separately for the system virtual-address map.'
call Emit 'VMAP shows area placement and size; it does not show usage %.'
return


VmapReport:
call Section 'SYSTEM VIRTUAL STORAGE MAP (SDSF VMAP)'

if \vmapAvailable then do
  call Emit 'VMAP data was unavailable or not authorized.'
  call Emit 'This does not invalidate SYS or AS storage analysis.'
  return
end

call Emit 'Area             Start-Address     End-Address       Size'
call Emit copies('-',66)

do i = 1 to vmapRows
  line = left(vmapName.i,16) ,
         left(vmapStart.i,17) ,
         left(vmapEnd.i,17) ,
         right(vmapSize.i,8)
  call Emit line
end

call Emit ''
call Emit 'Interpretation: VMAP describes the system virtual-storage'
call Emit 'layout. Combine its area sizes with SYS utilization and AS'
call Emit 'private-storage percentages; do not treat VMAP size as use.'
return


PrintTopPrivate:
call Section 'TOP PRIVATE VIRTUAL-STORAGE CONSUMERS'
call Emit 'Rank Jobname  Type ASID   Priv%  EPriv%  MemObjMB  MemLimit'
call Emit copies('-',72)

used. = 0
shown = 0

do rank = 1 to topN
  best = 0
  bestVal = -1

  do i = 1 to asRows
    if used.i then iterate

    metricVal = asPrivNum.i
    if asEPrivNum.i > metricVal then metricVal = asEPrivNum.i

    if metricVal > bestVal then do
      best = i
      bestVal = metricVal
    end
  end

  if best = 0 then leave
  if bestVal <= 0 then leave

  used.best = 1
  shown = shown + 1

  line = right(shown,4) ,
         left(asName.best,8) ,
         left(asType.best,4) ,
         right(asAsid.best,5) ,
         right(asPrivPct.best,7) ,
         right(asEPrivPct.best,7) ,
         right(FormatBig(asMObj.best),9) ,
         right(asMemLimit.best,9)

  call Emit line
end

if shown = 0 then
  call Emit 'No non-zero private virtual-storage usage was returned.'
return


PrintTopSimple:
parse arg psMetric, psTitle
call Section psTitle

select
  when psMetric = 'REAL' then unit = 'FRAMES'
  when psMetric = 'AUX' then unit = 'SLOTS'
  when psMetric = 'FIXED' then unit = 'FRAMES'
  when psMetric = 'MOBJ' then unit = 'MB'
  otherwise unit = 'VALUE'
end

call Emit 'Rank Jobname  Type ASID  Metric                 'unit
call Emit copies('-',66)

used. = 0
shown = 0

do rank = 1 to topN
  best = 0
  bestVal = -1

  do i = 1 to asRows
    if used.i then iterate

    select
      when psMetric = 'REAL' then metricVal = asReal.i
      when psMetric = 'AUX' then metricVal = asAux.i
      when psMetric = 'FIXED' then metricVal = asFixed.i
      when psMetric = 'MOBJ' then metricVal = asMObj.i
      otherwise metricVal = 0
    end

    if metricVal > bestVal then do
      best = i
      bestVal = metricVal
    end
  end

  if best = 0 then leave
  if bestVal <= 0 then leave

  used.best = 1
  shown = shown + 1

  line = right(shown,4) ,
         left(asName.best,8) ,
         left(asType.best,4) ,
         right(asAsid.best,5) ,
         right(FormatBig(bestVal),20)

  call Emit line
end

if shown = 0 then call Emit 'No non-zero consumers were returned.'
return


PrintTopCommon:
call Section 'TOP COMMON-STORAGE CONSUMERS'
call Emit 'Rank Jobname  ASID     CSA     ECSA      SQA     ESQA    Total'
call Emit copies('-',72)

used. = 0
shown = 0

do rank = 1 to topN
  best = 0
  bestVal = -1

  do i = 1 to asRows
    if used.i then iterate

    metricVal = asCSA.i + asECSA.i + asSQA.i + asESQA.i

    if metricVal > bestVal then do
      best = i
      bestVal = metricVal
    end
  end

  if best = 0 then leave
  if bestVal <= 0 then leave

  used.best = 1
  shown = shown + 1

  line = right(shown,4) ,
         left(asName.best,8) ,
         right(asAsid.best,5) ,
         right(HumanBytes(asCSA.best),8) ,
         right(HumanBytes(asECSA.best),8) ,
         right(HumanBytes(asSQA.best),8) ,
         right(HumanBytes(asESQA.best),8) ,
         right(HumanBytes(bestVal),8)

  call Emit line
end

if shown = 0 then call Emit 'No common-storage consumers were returned.'
return


MessageReport:
call Section 'MESSAGE EVIDENCE'

if msgAvail = 0 then do
  call Emit 'SYSLOG could not be searched or was not authorized.'
  call Emit 'The rest of STORDIAG remains valid without this section.'
  return
end

if msgCount = 0 then do
  call Emit 'No matching current-day IRA storage messages were found.'
  return
end

call KV 'Matching messages found', msgCount
call KV 'Messages displayed', msgStored

do i = 1 to msgStored
  call Emit left(i'.',4) msgLine.i
end

if msgCount > msgStored then
  call Emit 'Additional matching messages were omitted from this report.'
return


Analysis:
call Section 'ANALYSIS / INTERPRETATION'

if commonPeak >= 95 then do
  call Emit 'Common-storage pressure is the strongest current finding.'
  call Emit highCommonName 'reached' FormatPct(commonPeak) ,
            'during sampling.'
end
else if commonPeak >= 90 then do
  call Emit 'Common-storage use is elevated and should be watched.'
  call Emit highCommonName 'was the highest area at' ,
            FormatPct(commonPeak)'.'
end
else do
  call Emit 'No severe common-storage utilization was sampled.'
end

call Emit ''

if peakAux >= 95 then do
  call Emit 'Auxiliary-storage utilization is very high.'
  call Emit 'Verify page data-set status before taking recovery action.'
end
else if peakAux >= 90 then do
  call Emit 'Auxiliary-storage utilization is elevated.'
  call Emit 'Review page data-set status and the top AUX consumers.'
end
else do
  call Emit 'No severe auxiliary-storage utilization was sampled.'
end

call Emit ''

if peakPage > 0 then do
  call Emit 'Paging occurred during the sample window.'
  call Emit 'Paging rate is system-dependent; STORDIAG does not use a'
  call Emit 'universal page-rate threshold to declare a shortage.'
end
else do
  call Emit 'No paging activity was observed in the sample window.'
end

call Emit ''

call Emit 'Real-storage percentage is not treated as a shortage by'
call Emit 'itself. z/OS normally uses available real memory aggressively.'
call Emit 'Available-frame movement, paging, AUX and common storage'
call Emit 'must be considered together.'

if asAvailable & asLevel = 3 then do
  call Emit ''
  if peakPriv >= 95 | peakEPriv >= 95 then do
    call Emit 'At least one address space has very high private'
    call Emit 'virtual-storage utilization. Review the private-storage'
    call Emit 'ranking and drill into that address space with SDSF JM.'
  end
  else if peakPriv >= 90 | peakEPriv >= 90 then do
    call Emit 'Private virtual-storage use is elevated in at least one'
    call Emit 'address space, but no 95% virtual threshold was crossed.'
  end
  else do
    call Emit 'No address space crossed the private virtual-storage'
    call Emit 'triage thresholds during this snapshot.'
  end

  call Emit 'VMAP is layout evidence only; AS Priv% and EPriv% provide'
  call Emit 'the address-space virtual-storage usage percentages.'
end

if availDropPct >= 20 & peakPage > 0 then do
  call Emit ''
  call Emit 'Available frames fell materially while paging was active.'
  call Emit 'This warrants correlation with workload and I/O activity.'
end

if msgCritical then do
  call Emit ''
  call Emit 'A critical IRA storage message occurred in current-day'
  call Emit 'SYSLOG. Confirm whether it is still active or was relieved.'
end
return


Recommendations:
call Section 'RECOMMENDED DEBUGGING / POSSIBLE RESOLUTION'

call Emit '1. Review the ranked address spaces before targeting work.'
call Emit '   A large consumer is not automatically the root cause.'

if peakAux >= 90 | msgCount > 0 then do
  call Emit '2. Use D ASM to display active page data sets and SCM.'
  call Emit '   Check type, percent full, status and device.'
  call Emit '3. If an actual AUX shortage exists, PAGEADD may provide'
  call Emit '   temporary relief only when an approved, preformatted'
  call Emit '   page data set exists and PAGTOTL permits expansion.'
  call Emit '   Example syntax: PA PAGE=dsname'
  call Emit '   Do not issue PAGEADD merely because Aux% is high.'
end
else do
  call Emit '2. If storage symptoms continue, use D ASM to verify'
  call Emit '   paging-space status even when Aux% is currently normal.'
end

if commonPeak >= 90 | msgCount > 0 then do
  call Emit '4. For CSA/SQA pressure, inspect the common-storage'
  call Emit '   rankings and correlate any IRA100E/101E/103I messages.'
  call Emit '5. Use D VIRTSTOR for virtual-storage configuration.'
  call Emit '   Do not change SQA/CSA sizing solely from this snapshot.'
end
else do
  call Emit '4. If common-storage growth is suspected, rerun STORDIAG'
  call Emit '   during the problem and compare the same consumers.'
end

call Emit '6. For a confirmed shortage, preserve evidence before'
call Emit '   canceling a major consumer. IBM guidance for several IRA'
call Emit '   shortages includes obtaining an SVC dump of the affected'
call Emit '   address space for analysis.'
call Emit '7. If Priv% or EPriv% is high, use SDSF JM for the'
call Emit '   affected address space to inspect its storage/subpools.'
call Emit '   Check REGION/site IEFUSI policy and application growth.'
call Emit '   There is no generic safe dynamic command that simply'
call Emit '   increases a running address space private region.'
call Emit '8. VMAP is configuration/layout context, not a use counter.'
call Emit '   Use SYS percentages and AS private percentages for use.'
call Emit '9. If paging is active without a storage shortage, continue'
call Emit '   with I/O and WLM diagnostics rather than blaming storage.'
return


CommandReference:
call Section 'COMMAND REFERENCE'

call Emit 'DISPLAY / READ-ONLY COMMANDS'
call Emit '  D ASM'
call Emit '      Active page data sets, SCM and paging-space status.'
call Emit '  D ASM,LOCAL'
call Emit '      Local page data sets currently used by the system.'
call Emit '  D VIRTSTOR'
call Emit '      Current z/OS virtual-storage configuration.'
call Emit '  D M=STOR'
call Emit '      Central-storage configuration and online status.'
call Emit ''
call Emit 'SDSF PANELS USED / RECOMMENDED'
call Emit '  SYS   System storage and paging context.'
call Emit '  AS    Address-space storage utilization.'
call Emit '  VMAP  System virtual-address layout.'
call Emit '  JM    Drill into one address space when Priv/EPriv is high.'
call Emit ''
call Emit 'RECOVERY / CHANGE COMMAND - MANUAL REVIEW REQUIRED'
call Emit '  PAGEADD PAGE=<approved.page.data.set>'
call Emit '      Possible temporary relief for a confirmed AUX shortage.'
call Emit '      The page data set must already be suitable and approved.'
call Emit ''
call Emit 'STORDIAG never issues the recovery/change command above.'
call Emit 'There is no generic safe MODIFY/F command for private virtual'
call Emit 'storage. Any application-specific F command must come from the'
call Emit 'owning product procedure or runbook.'
return


Notes:
call Section 'IMPORTANT NOTES'
call Emit '  * STORDIAG is read-only.'
call Emit '  * SDSF SYS provides system storage and paging context.'
call Emit '  * SDSF AS ALL provides address-space storage consumption.'
call Emit '  * SDSF VMAP provides system virtual-storage layout.'
call Emit '  * VMAP size is not virtual-storage utilization.'
call Emit '  * SYSLOG scanning is optional and current-day only.'
call Emit '  * Results are limited by the caller''s SDSF/SAF visibility.'
call Emit '  * 90%/95% utilization levels are triage heuristics.'
call Emit '  * They are not IBM-defined universal shortage thresholds.'
call Emit '  * Real-storage use alone never drives a HIGH condition.'
call Emit '  * No recovery command is automatically issued.'
call Emit '  * Report remains in userid.STORDIAG.RPT after Browse.'
call Emit '  * The next STORDIAG run replaces that latest report.'
call Emit '  * Under ISPF the report opens in Browse.'
return


Footer:
call Emit ''
call Emit copies('=',78)
call Emit ' STORDIAG completed. No system or workload changes were made.'
call Emit copies('=',78)
return


HelpReport:
call Emit ''
call Emit copies('=',78)
call Emit ' STORDIAG - README / HELP'
call Emit copies('=',78)

call Emit ''
call Emit 'WHAT IS STORDIAG?'
call Emit copies('-',78)
call Emit 'STORDIAG is a read-only z/OS storage first-response utility.'
call Emit 'It samples system storage, paging and address-space consumers'
call Emit 'and produces one interpreted report for initial triage.'

call Emit ''
call Emit 'WHEN TO USE IT'
call Emit copies('-',78)
call Emit '  * An LPAR or workload is slow and storage is suspected.'
call Emit '  * Auxiliary-storage or paging pressure is suspected.'
call Emit '  * CSA, ECSA, SQA or ESQA pressure is suspected.'
call Emit '  * A job/STC may be exhausting private virtual storage.'
call Emit '  * You need the largest current storage consumers quickly.'

call Emit ''
call Emit 'WHAT IT CHECKS'
call Emit copies('-',78)
call Emit '  * Real frames online/available and fixed frames.'
call Emit '  * Paging rate and auxiliary-storage utilization.'
call Emit '  * CSA, ECSA, SQA and ESQA utilization.'
call Emit '  * Address-space REAL, AUX and common-storage consumers.'
call Emit '  * Priv% below 16 MB and EPriv% above 16 MB.'
call Emit '  * 64-bit memory-object usage when SDSF supplies it.'
call Emit '  * SDSF VMAP system virtual-storage layout.'
call Emit '  * Current-day IRA storage messages when SYSLOG is available.'

call Emit ''
call Emit 'HOW VMAP IS USED'
call Emit copies('-',78)
call Emit 'VMAP shows the virtual-address layout and size of system areas.'
call Emit 'It does not show the percentage currently consumed.'
call Emit 'STORDIAG uses SYS for common-area percentages and AS for'
call Emit 'address-space private virtual-storage percentages.'

call Emit ''
call Emit 'USAGE'
call Emit copies('-',78)
call Emit '  TSO STORDIAG'
call Emit '  TSO STORDIAG topRows'
call Emit '  TSO STORDIAG topRows samples intervalSeconds'
call Emit '  TSO STORDIAG SAMPLE'
call Emit '  TSO STORDIAG HELP'
call Emit '  TSO STORDIAG H'
call Emit '  TSO STORDIAG ?'

call Emit ''
call Emit 'DEFAULTS / LIMITS'
call Emit copies('-',78)
call KV 'Top rows', 'Default 10. Allowed 1-25.'
call KV 'Samples', 'Default 5. Allowed 3-12.'
call KV 'Sample interval', 'Default 10 sec. Allowed 5-60 sec.'

call Emit ''
call Emit 'SAMPLE MODE'
call Emit copies('-',78)
call Emit 'TSO STORDIAG SAMPLE creates a demonstration report so a user'
call Emit 'can see the report layout, interpretations and commands before'
call Emit 'running a live diagnostic.'
call Emit ''
call Emit 'SAMPLE mode does not read SDSF or SYSLOG and does not issue'
call Emit 'console commands. System name, date, time and documented'
call Emit 'command syntax are real; diagnostic values are sample data.'
call Emit ''
call Emit 'Sample output is saved in userid.STORDIAG.SAMPLE.'

call Emit ''
call Emit 'STANDARD REPORT STRUCTURE'
call Emit copies('-',78)
call Emit '  1. TRIAGE SUMMARY'
call Emit '  2. Diagnostic evidence / status sections'
call Emit '  3. Ranked consumers or affected resources'
call Emit '  4. MESSAGE EVIDENCE'
call Emit '  5. ANALYSIS / INTERPRETATION'
call Emit '  6. RECOMMENDED DEBUGGING / POSSIBLE RESOLUTION'
call Emit '  7. COMMAND REFERENCE'
call Emit '  8. IMPORTANT NOTES'

call Emit ''
call Emit 'REPORT OUTPUT'
call Emit copies('-',78)
call Emit 'Live report   : userid.STORDIAG.RPT'
call Emit 'Sample report : userid.STORDIAG.SAMPLE'
call Emit 'README / HELP : userid.STORDIAG.HELP'
call Emit 'Each data set remains cataloged after Browse is exited.'

call Emit ''
call Emit 'SAFETY'
call Emit copies('-',78)
call Emit 'STORDIAG is read-only. It never issues PAGEADD, CANCEL, DUMP,'
call Emit 'CONFIG STOR, MODIFY/F or other recovery/change commands.'
call Emit 'Commands shown in a report are recommendations for manual'
call Emit 'review and must follow site procedures and authorization.'

call Emit ''
call Emit 'LIMITATIONS'
call Emit copies('-',78)
call Emit '  * SDSF access controls the live evidence STORDIAG can see.'
call Emit '  * VMAP is a layout map, not a utilization counter.'
call Emit '  * 90%/95% levels are triage heuristics, not IBM thresholds.'
call Emit '  * A large consumer is not automatically the root cause.'

call Emit ''
call Emit copies('=',78)
return


/*====================================================================*/
/* HELPERS                                                            */
/*====================================================================*/

Section:
parse arg title
call Emit ''
call Emit copies('-',78)
call Emit ' 'title
call Emit copies('-',78)
return


KV:
parse arg kvLabel, kvValue
call Emit ' 'left(kvLabel,31)':' kvValue
return


Emit:
procedure expose rpt. rptCount
out = ''
do ei = 1 to arg()
  piece = arg(ei)
  if ei = 1 then out = piece
  else out = out piece
end
rptCount = rptCount + 1
rpt.rptCount = out
rpt.0 = rptCount
return


ValueOr:
procedure
parse arg v, d
v = strip(v)
if v = '' then return d
return v


Num:
procedure
numeric digits 20
parse arg n
n = translate(strip(n),'  ', ',%')
n = space(n,0)
if n = '' then return 0
if datatype(n,'N') then return n
return 0


ScaledNum:
procedure
numeric digits 20
parse upper arg n
n = translate(strip(n),' ', ',')
n = space(n,0)
if n = '' | n = 'N/A' | n = '-' then return 0

mult = 1
ln = length(n)

if ln >= 2 then do
  suf2 = right(n,2)

  select
    when suf2 = 'KB' then do
      mult = 1024
      n = left(n,ln-2)
    end
    when suf2 = 'MB' then do
      mult = 1024 * 1024
      n = left(n,ln-2)
    end
    when suf2 = 'GB' then do
      mult = 1024 * 1024 * 1024
      n = left(n,ln-2)
    end
    when suf2 = 'TB' then do
      mult = 1024 * 1024 * 1024 * 1024
      n = left(n,ln-2)
    end
    otherwise nop
  end
end

if mult = 1 & length(n) > 1 then do
  suf1 = right(n,1)

  select
    when suf1 = 'K' then do
      mult = 1024
      n = left(n,length(n)-1)
    end
    when suf1 = 'M' then do
      mult = 1024 * 1024
      n = left(n,length(n)-1)
    end
    when suf1 = 'G' then do
      mult = 1024 * 1024 * 1024
      n = left(n,length(n)-1)
    end
    when suf1 = 'T' then do
      mult = 1024 * 1024 * 1024 * 1024
      n = left(n,length(n)-1)
    end
    otherwise nop
  end
end

if datatype(n,'N') then return n * mult
return 0


FormatPct:
procedure
parse arg n
if datatype(strip(n),'N') = 0 then return n
return strip(format(n,7,2))'%'


ShortPct:
procedure
parse arg n
if datatype(strip(n),'N') = 0 then return 'N/A'
return strip(format(n,4,1))


FormatNum:
procedure
parse arg n, dec
if datatype(strip(n),'N') = 0 then return n
return strip(format(n,,dec))


FormatBig:
procedure
numeric digits 20
parse arg n
if datatype(strip(n),'N') = 0 then return n
return strip(format(n,20,0))


HumanBytes:
procedure
numeric digits 20
parse arg n
if datatype(strip(n),'N') = 0 then return n
if n < 1024 then return strip(format(n,7,0))'B'
if n < 1024 * 1024 then
  return strip(format(n / 1024,6,1))'K'
if n < 1024 * 1024 * 1024 then
  return strip(format(n / (1024 * 1024),6,1))'M'
if n < 1024 * 1024 * 1024 * 1024 then
  return strip(format(n / (1024 * 1024 * 1024),6,1))'G'
return strip(format(n / (1024 * 1024 * 1024 * 1024),6,1))'T'


AddPct:
procedure
parse arg n
n = strip(n)
if n = '' then return 'N/A'
if right(n,1) = '%' then return n
if datatype(n,'N') then return n'%'
return n


StorageMsgId:
procedure
parse upper arg text

ids = 'IRA100E IRA101E IRA103I IRA200E IRA201E IRA206I' ,
      'IRA210E IRA211I IRA220I IRA400E IRA401E IRA403E IRA404I'

do i = 1 to words(ids)
  id = word(ids,i)
  if pos(id,text) > 0 then return id
end

return ''


DisplayDate:
procedure
d = date('S')
yyyy = left(d,4)
mm = substr(d,5,2)
dd = right(d,2)
mons = 'JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC'
mon = word(mons,mm + 0)
return dd'-'mon'-'yyyy


SleepSeconds:
parse arg slSeconds
if slSeconds <= 0 then return

Address SYSCALL "sleep" slSeconds

if symbol('RETVAL') = 'VAR' then do
  if retval > 0 then do
    slRemain = retval
    Address SYSCALL "sleep" slRemain
  end
end
return


SdsfMessages:
if symbol('ISFMSG') <> 'LIT' then do
  if strip(isfmsg) <> '' then say ' SDSF:' isfmsg
end

if symbol('ISFMSG2.0') <> 'LIT' then do
  do sm = 1 to isfmsg2.0
    say ' SDSF:' isfmsg2.sm
  end
end
return


PresentReport:
procedure expose rpt. rptCount ispfActive reportKind
/* ZDIAG BATCH BYPASS */
if sysvar('SYSISPF') <> 'ACTIVE' then do
  if rptCount = 0 then return
  address TSO 'EXECIO 'rptCount' DISKW ZREPORT (STEM rpt. FINIS)'
  zdbw = rc
  if zdbw <> 0 then do
    say 'ZDIAG BATCH: ZREPORT write failed RC='zdbw
    exit 20
  end
  return
end
if rptCount = 0 then return

uid = sysvar('SYSUID')

if reportKind = 'HELP' then
  rptDsn = uid'.STORDIAG.HELP'
else if reportKind = 'SAMPLE' then
  rptDsn = uid'.STORDIAG.SAMPLE'
else
  rptDsn = uid'.STORDIAG.RPT'

ddn = 'SDRPT001'

zz = outtrap('sdQuiet.')
Address TSO "DELETE '"rptDsn"'"
zz = outtrap('OFF')

allocCmd = "ALLOC DA('"rptDsn"') F("ddn") NEW CATALOG"
allocCmd = allocCmd "TRACKS SPACE(5 5) DSORG(PS)"
allocCmd = allocCmd "RECFM(F B) LRECL(133) BLKSIZE(0)"

Address TSO allocCmd
aRc = rc

if aRc <> 0 then do
  say 'STORDIAG: Could not allocate persistent output data set.'
  say 'Requested data set:' rptDsn

  do pi = 1 to rptCount
    say rpt.pi
  end

  return
end

Address TSO "EXECIO "rptCount" DISKW" ddn "(STEM rpt. FINIS)"
wRc = rc
Address TSO "FREE F("ddn")"

if wRc <> 0 then do
  Address TSO "DELETE '"rptDsn"'"
  say 'STORDIAG: Could not write persistent output data set.'

  do pi = 1 to rptCount
    say rpt.pi
  end

  return
end

if \ispfActive then do
  do pi = 1 to rptCount
    say rpt.pi
  end

  say
  say 'STORDIAG: Output saved in' rptDsn
  return
end

Address ISPEXEC "CONTROL ERRORS RETURN"
Address ISPEXEC "BROWSE DATASET('"rptDsn"')"
bRc = rc
Address ISPEXEC "CONTROL ERRORS CANCEL"

if bRc > 4 then do
  say 'STORDIAG: ISPF Browse failed RC='bRc

  do pi = 1 to rptCount
    say rpt.pi
  end
end

say 'STORDIAG: Output saved in' rptDsn
return


Cleanup:
if ussOn = 1 then do
  tmp = syscalls('OFF')
  ussOn = 0
end

if sdsfOn = 1 then do
  tmp = isfcalls('OFF')
  sdsfOn = 0
end
return


FatalSyntax:
say
say 'STORDIAG: REXX syntax/runtime error.'
say 'Condition:' condition('C')
say 'Description:' condition('D')
say 'Source line:' sigl
if datatype(sigl,'W') then say 'Statement:' sourceline(sigl)
if symbol('SDSFON') <> 'LIT' then call Cleanup
exit 20
