/* REXX */
/*--------------------------------------------------------------------*/
/* JES2DIAG - JES2 health and delay diagnostic                         */
/*                                                                    */
/* Author  : SARATH S                                                 */
/* Version : 1.2                                                      */
/* Purpose : Correlate JES2 Health Monitor alerts, PCE delays,        */
/*           checkpoint/MAS state, CPU context, XCF evidence and      */
/*           concurrent JES2 resource shortages.                      */
/*                                                                    */
/* Usage:                                                             */
/*   TSO JES2DIAG                                                     */
/*   TSO JES2DIAG 60                                                  */
/*   TSO JES2DIAG 9203 60                                             */
/*   TSO JES2DIAG 9207 60                                             */
/*   TSO JES2DIAG CKPT 60                                             */
/*   TSO JES2DIAG HEALTH 30                                           */
/*   TSO JES2DIAG SAMPLE                                              */
/*   TSO JES2DIAG HELP                                                */
/*                                                                    */
/* Design:                                                            */
/* - Single member and site-neutral.                                  */
/* - Read-only. Only DISPLAY and JES2 monitor/display commands.       */
/* - Uses supported SDSF REXX services.                               */
/* - Never changes JES2, checkpoint, MAS, XCF or system settings.     */
/* - Recent SYSLOG analysis is bounded by time and line count.        */
/* - Current command evidence is collected only when relevant.        */
/* - SAMPLE builds a static demonstration report with no live calls.  */
/* - Report data sets are retained after ISPF Browse is exited.       */
/*--------------------------------------------------------------------*/

signal on syntax name FatalSyntax

/*--------------------------------------------------------------------*/
/* Defaults and argument parsing                                      */
/*--------------------------------------------------------------------*/
lookback = 30
mode = 'AUTO'
reportKind = 'RPT'
maxLogLines = 20000
maxContextEvents = 3
maxContextBefore = 2
maxContextAfter = 4

rpt. = ''
rpt.0 = 0
rptCount = 0
cmdLine. = ''
cmdTag. = ''
cmdLineCount = 0
logLine. = ''
logCount = 0
logTruncated = 0
ispfActive = 0
sdsfOn = 0
localSys = translate(MVSVAR('SYSNAME'))
startClock = time('N')

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

parse upper arg p1 p2 .
p1 = strip(p1)
p2 = strip(p2)

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

if p1 = 'SAMPLE' then do
  reportKind = 'SAMPLE'
  mode = 'SAMPLE'
  call SampleReport
  call PresentReport
  exit 0
end

if left(p1,5) = '$HASP' then p1 = substr(p1,6)

validMode = ' AUTO 9203 9204 9207 9213 CKPT HEALTH '

if p1 <> '' then do
  if pos(' 'p1' ',validMode) > 0 then mode = p1
  else if datatype(p1,'W') then lookback = p1
  else do
    call Emit 'JES2DIAG: Unknown option:' p1
    call Emit 'Use TSO JES2DIAG HELP'
    call PresentReport
    exit 12
  end
end

if p2 <> '' then do
  if datatype(p2,'W') = 0 then do
    call Emit 'JES2DIAG: Lookback minutes must be a whole number.'
    call PresentReport
    exit 12
  end
  lookback = p2
end

if datatype(lookback,'W') = 0 then do
  call Emit 'JES2DIAG: Lookback minutes must be a whole number.'
  call PresentReport
  exit 12
end

if lookback < 5 then lookback = 5
if lookback > 180 then lookback = 180

/*--------------------------------------------------------------------*/
/* Message counters and event data                                    */
/*--------------------------------------------------------------------*/
idList = '050 1405 263 9201 9202 9203 9204 9205 9206 9207 9208' ,
         '9209 9210 9211 9212 9213 9301 9302'

msgCount. = 0
msgLastIx. = 0
msgFirstIx. = 0
relIx. = 0
relCount = 0

do ii = 1 to words(idList)
  id = word(idList,ii)
  msgCount.id = 0
end

eCount = 0
eIx. = 0
eDuration. = ''
eSeconds. = 0
ePce. = ''
eJob. = ''
eExit. = ''
eCommand. = ''

pceCount. = 0
pceLabel. = ''
pceKeys.0 = 0

shortRes. = 0
shortResLabel. = ''
shortResKeys.0 = 0

cpuAvail = 0
cpuPct = 0
pageRate = ''
sioRate = ''
auxPct = ''
realAvail = ''

currentMonitor = 'UNKNOWN'
currentAlert = 0
currentTracking = 0
currentClear = 0

memberRows = 0
memberBad = 0
memberDormant = 0
memberStatusText = ''
xcfGroup = ''
xcfCollected = 0
xcfExplicitStall = 0

ckptAvgTimeMax = 0
ckptAvgTimeSeen = 0
ckptAvgHold = ''
ckptAvgDorm = ''
ckptHolder = ''

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

isfprefix = '**'
isfowner = '*'
isfsysname = '*'
isfdelay = '2'

/* Read the bounded recent log before issuing any display commands. */
call ReadRecentSyslog
call AnalyzeRecentLog
call FetchSystemContext

/* Determine which diagnostic branches are justified by evidence. */
branchPCE = 0
branchCKPT = 0
branchXCF = 0
branchPerf = 0

if msgCount.9203 > 0 | msgCount.9204 > 0 then branchPCE = 1
if msgCount.9213 > 0 then branchPCE = 1
if mode = '9203' | mode = '9204' | mode = '9213' then branchPCE = 1

if msgCount.9207 > 0 | msgCount.263 > 0 then branchCKPT = 1
if mode = '9207' | mode = 'CKPT' then branchCKPT = 1

call FindDominantPce
if dominantPce = 'CKPT' then branchCKPT = 1
if pos('XCF',dominantPce) > 0 then branchXCF = 1
if branchCKPT then branchXCF = 1
if branchPCE | branchCKPT then branchPerf = 1

/*--------------------------------------------------------------------*/
/* Current JES2 Health Monitor state                                  */
/*--------------------------------------------------------------------*/
call IssueCommand 'JESMON', '$JD JES'
call AnalyzeMonitorRange lastCmdStart,lastCmdEnd

call IssueCommand 'JDSTAT', '$JD STATUS'
call AnalyzeMonitorRange lastCmdStart,lastCmdEnd

if branchPCE | branchCKPT | mode = 'HEALTH' then do
  call IssueCommand 'JDDET', '$JD DETAILS'
  call AnalyzeMonitorRange lastCmdStart,lastCmdEnd
end

/* MAS member state is low-cost and useful for JES2 health context. */
call IssueCommand 'MEMBER', '$D MEMBER(*)'
call AnalyzeMemberRange lastCmdStart,lastCmdEnd

if branchPCE then do
  call IssueCommand 'PCE', '$D PCE'

  call IssueCommand 'EVENT', '$D PERFDATA(EVENT)'

  call IssueCommand 'PCESTAT', '$D PERFDATA(PCESTAT)'
end

if branchCKPT then do
  call IssueCommand 'CKPTDEF', '$D CKPTDEF'

  call IssueCommand 'MASDEF', '$D MASDEF'
  call AnalyzeMasRange lastCmdStart,lastCmdEnd

  call IssueCommand 'CKPTSTAT', '$D PERFDATA(CKPTSTAT)'
  call AnalyzeCkptPerfRange lastCmdStart,lastCmdEnd

  call IssueCommand 'QSUSE', '$D PERFDATA(QSUSE)'
end

if branchXCF & xcfGroup <> '' then do
  xcmd = 'D XCF,GROUP,'xcfGroup
  call IssueCommand 'XCF', xcmd
  call AnalyzeXcfRange lastCmdStart,lastCmdEnd
end

if currentAlert then currentMonitor = 'ACTIVE ALERT'
else if currentTracking then currentMonitor = 'TRACKING'
else if currentClear then currentMonitor = 'CLEAR'

/*--------------------------------------------------------------------*/
/* Render report                                                      */
/*--------------------------------------------------------------------*/
call Header
call TriageSummary
call IncidentSummary
call HealthMonitorReport
call PceReport
call CheckpointReport
call SystemContextReport
call ResourceCorrelation
call NearbyMessageReport
call Analysis
call Recommendations
call CommandReference
call RawEvidence
call ImportantNotes
call Footer

call Cleanup
call PresentReport
exit 0

/*====================================================================*/
/* LOG COLLECTION                                                     */
/*====================================================================*/
ReadRecentSyslog:
/* Read only the requested recent time window, in bounded chunks. */
nowDay = date('B')
nowSec = time('S')
startDay = nowDay
startSec = nowSec - (lookback * 60)

do while startSec < 0
  startSec = startSec + 86400
  startDay = startDay - 1
end

isfdate = 'mmddyyyy /'
isflogstartdate = SdsfDate(startDay)
isflogstarttime = SecToClock(startSec)
isflogstopdate = SdsfDate(nowDay)
isflogstoptime = time('N')
isflinelim = 500

drop isfstartlinetoken
drop isfnextlinetoken
drop isfline.

readLoops = 0
do forever
  Address SDSF "ISFLOG READ TYPE(SYSLOG)"
  lrRc = rc

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

  logRc = 0
  readLoops = readLoops + 1

  if symbol('ISFLINE.0') = 'VAR' then do li = 1 to isfline.0
    if logCount >= maxLogLines then do
      logTruncated = 1
      leave
    end
    logCount = logCount + 1
    logLine.logCount = isfline.li
  end

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

  if readLoops > 100 then do
    logTruncated = 1
    leave
  end
end

drop isflogstartdate isflogstarttime
drop isflogstopdate isflogstoptime isflinelim
drop isfstartlinetoken isfnextlinetoken
return

/*====================================================================*/
/* LOG ANALYSIS                                                       */
/*====================================================================*/
AnalyzeRecentLog:
if logRc <> 0 then return

/* First count relevant messages and remember their log positions. */
do ai = 1 to logCount
  u = translate(logLine.ai)
  foundRelevant = 0

  do aj = 1 to words(idList)
    id = word(idList,aj)
    needle = '$HASP' || id
    if pos(needle,u) > 0 then do
      msgCount.id = msgCount.id + 1
      msgLastIx.id = ai
      if msgFirstIx.id = 0 then msgFirstIx.id = ai
      foundRelevant = 1
    end
  end

  if foundRelevant then do
    relCount = relCount + 1
    relIx.relCount = ai
  end

  if pos('$HASP263',u) > 0 then do
    hv = ExtractToken(u,'LOCK HELD BY MEMBER ')
    if hv <> '' then ckptHolder = hv
  end

  if pos('$HASP050',u) > 0 | pos('$HASP1405',u) > 0 then do
    sr = ShortageResource(u)
    if sr <> '' then do
      sk = c2x(sr)
      if shortRes.sk = 0 then do
        shortResKeys.0 = shortResKeys.0 + 1
        sx = shortResKeys.0
        shortResKeys.sx = sk
        shortResLabel.sk = sr
      end
      shortRes.sk = shortRes.sk + 1
    end
  end
end

/* Parse each 9203 record plus its immediate continuation lines. */
do ai = 1 to logCount
  u = translate(logLine.ai)
  if pos('$HASP9203',u) = 0 then iterate

  eCount = eCount + 1
  eIx.eCount = ai

  do aj = ai to min(logCount,ai + 4)
    du = translate(logLine.aj)

    if aj > ai & pos('$HASP',du) > 0 then leave

    if eDuration.eCount = '' then do
      val = ExtractToken(du,'DURATION-')
      if val <> '' then do
        eDuration.eCount = val
        eSeconds.eCount = DurationSeconds(val)
      end
    end

    if ePce.eCount = '' then do
      val = ExtractToken(du,'PCE-')
      if val <> '' then ePce.eCount = val
    end

    if eJob.eCount = '' then do
      val = ExtractToken(du,'JOB ID-')
      if val <> '' then eJob.eCount = val
    end

    if eExit.eCount = '' then do
      val = ExtractToken(du,'EXIT-')
      if val <> '' then eExit.eCount = val
    end

    if eCommand.eCount = '' then do
      val = ExtractRest(du,'COMMAND-')
      if val <> '' then eCommand.eCount = val
    end
  end

  pce = translate(strip(ePce.eCount))
  if pce <> '' then do
    pk = c2x(pce)
    if pceCount.pk = 0 then do
      pceKeys.0 = pceKeys.0 + 1
      px = pceKeys.0
      pceKeys.px = pk
      pceLabel.pk = pce
    end
    pceCount.pk = pceCount.pk + 1
  end
end
return

FindDominantPce:
dominantPce = ''
dominantPceCount = 0
maxDuration = 0
maxDurationText = ''
maxDurationEvent = 0

if eCount > 0 then do ei = 1 to eCount
  if eSeconds.ei > maxDuration then do
    maxDuration = eSeconds.ei
    maxDurationText = eDuration.ei
    maxDurationEvent = ei
  end
end

do pi = 1 to pceKeys.0
  pk = pceKeys.pi
  if pceCount.pk > dominantPceCount then do
    dominantPceCount = pceCount.pk
    dominantPce = pceLabel.pk
  end
end
return

/*====================================================================*/
/* SYSTEM CONTEXT                                                     */
/*====================================================================*/
FetchSystemContext:
drop SYSNAME. CPUPR. PAGERATE. SIO. AUXPCT. REALAFC.

isfcols = 'SYSNAME CPUPR PAGERATE SIO AUXPCT REALAFC'
Address SDSF "ISFEXEC SYS"
fsRc = rc

if fsRc <> 0 then do
  drop SYSNAME. CPUPR. PAGERATE. SIO.
  isfcols = 'SYSNAME CPUPR PAGERATE SIO'
  Address SDSF "ISFEXEC SYS"
  fsRc = rc
end

if fsRc <> 0 then do
  drop SYSNAME. CPUPR.
  isfcols = 'SYSNAME CPUPR'
  Address SDSF "ISFEXEC SYS"
  fsRc = rc
end

if fsRc <> 0 then return
if isfrows = 0 then return

pick = 0
do fi = 1 to isfrows
  sysv = GetStemValue('SYSNAME',fi,'')
  if translate(strip(sysv)) = localSys then do
    pick = fi
    leave
  end
end
if pick = 0 then pick = 1

cpuAvail = 1
cpuPct = Num(GetStemValue('CPUPR',pick,'0'))
pageRate = GetStemValue('PAGERATE',pick,'')
sioRate = GetStemValue('SIO',pick,'')
auxPct = GetStemValue('AUXPCT',pick,'')
realAvail = GetStemValue('REALAFC',pick,'')
return

/*====================================================================*/
/* COMMAND COLLECTION                                                 */
/*====================================================================*/
IssueCommand:
parse arg icTag, icText

drop cmd.
drop isfulog.
drop isfmsg
drop isfmsg2.

cmd.0 = 1
cmd.1 = icText
lastCmdStart = cmdLineCount + 1

Address SDSF "ISFSLASH (cmd.) (WAIT)"
icRc = rc

cmdLineCount = cmdLineCount + 1
cmdLine.cmdLineCount = 'COMMAND: 'icText'  RC='icRc
cmdTag.cmdLineCount = icTag

if symbol('ISFULOG.0') = 'VAR' then do ci = 1 to isfulog.0
  cmdLineCount = cmdLineCount + 1
  cmdLine.cmdLineCount = isfulog.ci
  cmdTag.cmdLineCount = icTag
end

if symbol('ISFMSG') <> 'LIT' then do
  if strip(isfmsg) <> '' then do
    cmdLineCount = cmdLineCount + 1
    cmdLine.cmdLineCount = 'SDSF: 'isfmsg
    cmdTag.cmdLineCount = icTag
  end
end

if symbol('ISFMSG2.0') = 'VAR' then do ci = 1 to isfmsg2.0
  cmdLineCount = cmdLineCount + 1
  cmdLine.cmdLineCount = 'SDSF: 'isfmsg2.ci
  cmdTag.cmdLineCount = icTag
end

lastCmdEnd = cmdLineCount
return icRc

AnalyzeMonitorRange:
parse arg amStart, amEnd
if amEnd < amStart then return

do mi = amStart to amEnd
  u = translate(cmdLine.mi)

  if pos('NO OUTSTANDING ALERT',u) > 0 then currentClear = 1
  if pos('INCIDENTS BEING TRACKED',u) > 0 then currentTracking = 1
  if pos('OUTSTANDING ALERTS',u) > 0 & ,
     pos('NO OUTSTANDING ALERTS',u) = 0 then currentAlert = 1
end
return

AnalyzeMemberRange:
parse arg mrStart, mrEnd
if mrEnd < mrStart then return

do mi = mrStart to mrEnd
  u = translate(cmdLine.mi)
  if pos('STATUS=',u) = 0 then iterate

  st = ExtractValue(u,'STATUS=')
  if st = '' then iterate

  memberRows = memberRows + 1
  if memberStatusText = '' then memberStatusText = st
  else if wordpos(st,memberStatusText) = 0 then
    memberStatusText = memberStatusText st

  if st = 'DORMANT' then memberDormant = memberDormant + 1

  bad = ' JESXCF-FAILED JES2-FAILED MVS-GONE FAILED '
  if pos(' 'st' ',bad) > 0 then memberBad = memberBad + 1
end
return

AnalyzeMasRange:
parse arg maStart, maEnd
if maEnd < maStart then return

do mi = maStart to maEnd
  u = translate(cmdLine.mi)
  if pos('XCFGRPNM=',u) > 0 then do
    val = ExtractValue(u,'XCFGRPNM=')
    if val <> '' then xcfGroup = val
  end
end
return

AnalyzeXcfRange:
parse arg axStart, axEnd
if axEnd < axStart then return
xcfCollected = 1

do xi = axStart to axEnd
  u = translate(cmdLine.xi)
  if pos('STALLED',u) > 0 then xcfExplicitStall = 1
end
return

AnalyzeCkptPerfRange:
parse arg cpStart, cpEnd
if cpEnd < cpStart then return

do ci = cpStart to cpEnd
  u = translate(cmdLine.ci)

  if ckptAvgHold = '' then do
    v = ExtractValue(u,'AVGHOLD=')
    if v <> '' then ckptAvgHold = v
  end

  if ckptAvgDorm = '' then do
    v = ExtractValue(u,'AVGDORM=')
    if v <> '' then ckptAvgDorm = v
  end

  rest = u
  do while pos('AVGTIME=',rest) > 0
    p = pos('AVGTIME=',rest)
    rest = substr(rest,p)
    v = ExtractValue(rest,'AVGTIME=')
    if datatype(v,'N') then do
      ckptAvgTimeSeen = ckptAvgTimeSeen + 1
      if v > ckptAvgTimeMax then ckptAvgTimeMax = v
    end
    if length(rest) <= 8 then leave
    rest = substr(rest,9)
  end
end
return

/*====================================================================*/
/* REPORT                                                             */
/*====================================================================*/
Header:
call Emit ''
call Emit copies('=',78)
call Emit ' JES2DIAG - JES2 HEALTH / DELAY DIAGNOSTIC'
call KV 'Author', 'SARATH S'
call KV 'Date', DisplayDate()
call KV 'Time', startClock
call KV 'System', localSys
call KV 'Mode', mode
call KV 'Lookback', lookback'minutes'
call Emit copies('=',78)
return

TriageSummary:
call Section 'TRIAGE SUMMARY'

lockStrong = 0
if msgCount.9207 > 0 then lockStrong = 1
if msgCount.263 > 0 then lockStrong = 1

cpuState = 'UNKNOWN'
if cpuAvail then do
  if cpuPct >= 90 then cpuState = 'HIGH'
  else if cpuPct >= 80 then cpuState = 'ELEVATED'
  else cpuState = 'NO CURRENT SATURATION EVIDENCE'
end

finding = 'No JES2 health delay identified in the analysis window.'
confidence = 'MEDIUM'
path = 'No JES2-specific delay path is currently supported.'

if msgCount.9203 > 0 then do
  finding = 'JES2 PCE dispatch delay detected.'
  confidence = 'MEDIUM'
  path = 'PCE-specific investigation is required.'
end

if dominantPce = 'CKPT' & msgCount.9203 > 0 then do
  finding = 'JES2 checkpoint PCE delay detected.'
  confidence = 'MEDIUM'
  path = 'Checkpoint serialization / MAS / checkpoint I/O path.'
end

if dominantPce = 'CKPT' & lockStrong then do
  finding = 'Correlated JES2 checkpoint serialization delay detected.'
  confidence = 'HIGH'
  path = 'Checkpoint serialization / MAS access is the leading path.'
end

if msgCount.9207 > 0 & msgCount.9203 = 0 then do
  finding = 'JES2 checkpoint lock delay detected.'
  confidence = 'HIGH'
  path = 'Checkpoint holder, MAS member and checkpoint I/O path.'
end

if msgCount.9213 > 0 & dominantPce <> 'CKPT' then do
  finding = 'JES2 command-processing delay detected.'
  confidence = 'HIGH'
  path = 'JES2 command/PCE processing path.'
end

if dominantPce <> '' & pos('XCF',dominantPce) > 0 then do
  finding = 'JES2 XCF-related PCE delay detected.'
  confidence = 'HIGH'
  path = 'JES2 XCF command/member communication path.'
end

if msgCount.9204 > 0 & msgCount.9203 = 0 then do
  finding = 'JES2 main-task busy condition detected.'
  confidence = 'HIGH'
  path = 'Main-task/PCE activity requires correlation.'
end

shortTotal = msgCount.050 + msgCount.1405
delayTotal = msgCount.9203 + msgCount.9204 + msgCount.9207 + ,
             msgCount.9213
if shortTotal > 0 & delayTotal = 0 then do
  finding = 'JES2 resource pressure detected; health delay not proven.'
  confidence = 'HIGH'
  path = 'Use JOBDIAG for resource-consumer analysis.'
end

call KV 'Finding', finding
call KV 'Confidence', confidence
call KV 'Current monitor', currentMonitor

if msgCount.9203 > 0 then do
  call KV '$HASP9203 records', msgCount.9203
  if dominantPce <> '' then do
    dval = dominantPce' ('dominantPceCount' of 'eCount')'
    call KV 'Dominant PCE', dval
  end
  if maxDurationText <> '' then
    call KV 'Maximum duration', maxDurationText
end

call KV '$HASP9207 records', msgCount.9207
call KV '$HASP9213 records', msgCount.9213
call KV 'CPU context', cpuState

shortTotal = msgCount.050 + msgCount.1405
if shortTotal > 0 then call KV 'Resource shortage', 'YES - 'shortTotal' records'
else call KV 'Resource shortage', 'NO recent record found'

call KV 'Likely path', path
return

IncidentSummary:
call Section 'RECENT JES2 ALERT CORRELATION'

if logRc <> 0 then do
  call Emit 'Recent SYSLOG could not be read. RC='logRc
  call Emit 'Current JES2 display evidence is still shown below.'
  return
end

call KV 'SYSLOG lines read', logCount
call KV 'Relevant records', relCount
if logTruncated then call KV 'Log completeness', 'TRUNCATED AT SAFETY LIMIT'
else call KV 'Log completeness', 'COMPLETE FOR REQUESTED WINDOW'

call Emit ''
call Emit 'Message      Count   Interpretation'
call Emit copies('-',69)
call MsgRow '9201', 'JES2 main task in MVS wait'
call MsgRow '9202', 'JES2 main-task loop indication'
call MsgRow '9203', 'Long PCE dispatch'
call MsgRow '9204', 'JES2 main task busy'
call MsgRow '9207', 'JES2 checkpoint lock held'
call MsgRow '9208', 'JES2 local lock held'
call MsgRow '9209', 'JES2 non-dispatchable condition'
call MsgRow '9210', 'JES2 page-fault wait'
call MsgRow '9211', 'JES2 not running'
call MsgRow '9212', 'MVS not dispatching JES2'
call MsgRow '9213', 'Long JES2 command processing'
call MsgRow '050',  'JES2 resource shortage'
call MsgRow '1405', 'Privileged-resource shortage'
call MsgRow '263',  'Checkpoint-related message'
call MsgRow '9301', 'Main-task health alerts cleared'
call MsgRow '9302', 'Checkpoint lock alert cleared'

if msgCount.9203 > 1 then do
  call Emit ''
  call Emit 'NOTE: Repeated $HASP9203 records do not automatically mean'
  call Emit 'the same number of separate incidents. Persistent monitor'
  call Emit 'conditions can be reissued while one incident continues.'
end
return

HealthMonitorReport:
call Section 'CURRENT JES2 HEALTH / MAS STATE'
call KV 'Health Monitor', currentMonitor

if memberRows = 0 then do
  call KV 'MAS member data', 'UNAVAILABLE / NOT PARSED'
end
else do
  call KV 'Member rows seen', memberRows
  call KV 'Failed members', memberBad
  call KV 'Dormant members', memberDormant
  call KV 'Statuses observed', memberStatusText
end

if xcfGroup <> '' then call KV 'JES2 XCF group', xcfGroup
else call KV 'JES2 XCF group', 'NOT COLLECTED / NOT FOUND'

if xcfCollected then do
  if xcfExplicitStall then call KV 'XCF stall text', 'FOUND'
  else call KV 'XCF stall text', 'NO EXPLICIT STALLED TEXT FOUND'
end
return

PceReport:
call Section 'PCE / COMMAND ANALYSIS'

if eCount = 0 then do
  call Emit 'No $HASP9203 record was parsed in the requested window.'
  return
end

call KV '$HASP9203 records', eCount
call KV 'Dominant PCE', dominantPce
if maxDurationText <> '' then call KV 'Maximum duration', maxDurationText

call Emit ''
call Emit 'PCE        Records'
call Emit copies('-',28)
do pi = 1 to pceKeys.0
  pk = pceKeys.pi
  call Emit left(pceLabel.pk,10) right(pceCount.pk,7)
end

call Emit ''
call Emit 'Most recent parsed 9203 records'
call Emit copies('-',78)
startE = eCount - 4
if startE < 1 then startE = 1

do ei = startE to eCount
  line = 'Record 'ei
  if eDuration.ei <> '' then line = line'  Dur='eDuration.ei
  if ePce.ei <> '' then line = line'  PCE='ePce.ei
  call Emit line

  if eJob.ei <> '' then call Emit '  Job ID :' eJob.ei
  if eExit.ei <> '' then call Emit '  Exit   :' eExit.ei
  if eCommand.ei <> '' then call Emit '  Command:' eCommand.ei
end
return

CheckpointReport:
if \branchCKPT then return
call Section 'CHECKPOINT / MAS / XCF CORRELATION'

if msgCount.9207 > 0 then
  call KV '$HASP9207', msgCount.9207' records in window'
else call KV '$HASP9207', 'No recent record found'

if msgCount.263 > 0 then
  call KV '$HASP263', msgCount.263' records in window'
else call KV '$HASP263', 'No recent record found'

if ckptHolder <> '' then call KV 'Last parsed lock holder', ckptHolder

if ckptAvgHold <> '' then call KV 'PERFDATA AVGHOLD', ckptAvgHold
if ckptAvgDorm <> '' then call KV 'PERFDATA AVGDORM', ckptAvgDorm

if ckptAvgTimeSeen > 0 then do
  call KV 'Max CKPT AVGTIME', ckptAvgTimeMax
  call Emit 'AVGTIME is shown as supporting checkpoint I/O context.'
  call Emit 'JES2DIAG does not apply a site-independent latency threshold.'
end
else do
  call KV 'CKPT I/O timing', 'UNAVAILABLE / NOT PARSED'
end

if xcfGroup <> '' then call KV 'JES2 XCF group', xcfGroup
if xcfCollected then do
  if xcfExplicitStall then call KV 'Explicit XCF stall', 'YES'
  else call KV 'Explicit XCF stall', 'NOT FOUND IN DISPLAY TEXT'
end
return

SystemContextReport:
call Section 'CURRENT SYSTEM CONTEXT'

if \cpuAvail then do
  call Emit 'SDSF SYS CPU context was unavailable.'
  return
end

call KV 'System CPU%', FormatPct(cpuPct)
if pageRate <> '' then call KV 'Page rate', pageRate
if sioRate <> '' then call KV 'SIO rate', sioRate
if auxPct <> '' then call KV 'Aux%', auxPct
if realAvail <> '' then call KV 'Real frames available', realAvail

if cpuPct >= 90 then do
  call Emit ''
  call Emit 'Current CPU is high enough to remain a credible contributor.'
  call Emit 'Use CPUDIAG if sustained CPU pressure must be established.'
end
else if cpuPct >= 80 then do
  call Emit ''
  call Emit 'Current CPU is elevated but one snapshot cannot prove cause.'
end
else do
  call Emit ''
  call Emit 'Current CPU does not support global CPU saturation as the'
  call Emit 'primary explanation at the time JES2DIAG was executed.'
end
return

ResourceCorrelation:
call Section 'JES2 RESOURCE-PRESSURE CORRELATION'

shortTotal = msgCount.050 + msgCount.1405
if shortTotal = 0 then do
  call Emit 'No recent $HASP050 or $HASP1405 record was found.'
  return
end

call KV '$HASP050 records', msgCount.050
call KV '$HASP1405 records', msgCount.1405

if shortResKeys.0 > 0 then do
  call Emit ''
  call Emit 'Resource       Records'
  call Emit copies('-',30)
  do ri = 1 to shortResKeys.0
    rk = shortResKeys.ri
    call Emit left(shortResLabel.rk,12) right(shortRes.rk,7)
  end
end

call Emit ''
call Emit 'JES2DIAG correlates resource pressure with JES2 health delays.'
call Emit 'It intentionally does not duplicate JOBDIAG consumer analysis.'
call Emit 'Run JOBDIAG when the shortage itself needs root-cause analysis.'
return

NearbyMessageReport:
call Section 'NEARBY SYSLOG CONTEXT'

if logRc <> 0 then do
  call Emit 'SYSLOG context unavailable.'
  return
end

if relCount = 0 then do
  call Emit 'No monitored JES2 alert/resource message was found.'
  return
end

shown = 0
seenEvent. = 0

do ri = relCount to 1 by -1
  if shown >= maxContextEvents then leave
  centerIx = relIx.ri

  /* Avoid repeatedly printing heavily overlapping windows. */
  overlap = 0
  do si = 1 to shown
    if abs(centerIx - seenEvent.si) <= 3 then overlap = 1
  end
  if overlap then iterate

  shown = shown + 1
  seenEvent.shown = centerIx
  call Emit ''
  call Emit 'Context around relevant log record' centerIx
  call Emit copies('-',78)

  fromIx = centerIx - maxContextBefore
  if fromIx < 1 then fromIx = 1
  toIx = centerIx + maxContextAfter
  if toIx > logCount then toIx = logCount

  do li = fromIx to toIx
    call Emit FitLine(logLine.li,133)
  end
end
return

Analysis:
call Section 'ANALYSIS / INTERPRETATION'

lockStrong = 0
if msgCount.9207 > 0 | msgCount.263 > 0 then lockStrong = 1

if dominantPce = 'CKPT' & lockStrong then do
  call Emit 'The strongest correlation is a checkpoint-related JES2 delay.'
  call Emit 'Long CKPT PCE dispatch and checkpoint-lock evidence occurred'
  call Emit 'in the same analysis window. This supports checkpoint/MAS'
  call Emit 'serialization as the leading investigation path.'
end
else if dominantPce = 'CKPT' then do
  call Emit 'CKPT is the dominant long-dispatch PCE, but lock evidence is'
  call Emit 'not strong enough to call this a checkpoint-lock incident.'
end
else if dominantPce <> '' & pos('XCF',dominantPce) > 0 then do
  call Emit 'The dominant PCE points toward JES2 XCF communication.'
  call Emit 'Review MAS member and XCF evidence before changing JES2.'
end
else if msgCount.9213 > 0 then do
  call Emit 'The available evidence points toward JES2 command processing.'
  call Emit 'Use the command text and PCE data to identify the slow path.'
end
else if msgCount.9203 + msgCount.9204 > 0 then do
  call Emit 'A JES2 internal delay is present, but the current evidence does'
  call Emit 'not isolate one subsystem strongly enough for a high-confidence'
  call Emit 'root cause. Use the PCE and Health Monitor evidence below.'
end
else if msgCount.050 + msgCount.1405 > 0 then do
  call Emit 'Resource pressure is present without a proven JES2 internal'
  call Emit 'delay. JOBDIAG is the correct utility for consumer analysis.'
end
else do
  call Emit 'No JES2-specific health delay is proven in this time window.'
end

if msgCount.9203 > 1 | msgCount.9207 > 1 then do
  call Emit ''
  call Emit 'Repeated Health Monitor records can represent one continuing'
  call Emit 'condition. Message counts are evidence volume, not an incident'
  call Emit 'count unless recovery boundaries prove separate incidents.'
end
return


Recommendations:
call Section 'RECOMMENDED DEBUGGING / POSSIBLE RESOLUTION'

if dominantPce = 'CKPT' | msgCount.9207 > 0 then do
  call Emit '1. Treat checkpoint serialization/MAS access as the leading'
  call Emit '   path only when the correlated CKPT/9207 evidence supports it.'
  call Emit '2. Review the MEMBER, MASDEF, CKPTDEF, CKPTSTAT and XCF'
  call Emit '   evidence at the bottom of this report.'
  call Emit '3. Identify whether one member repeatedly holds checkpoint'
  call Emit '   access or whether checkpoint I/O timing is abnormal for site.'
  call Emit '4. If XCF status is suspicious, continue with XCFDIAG.'
end
else if dominantPce <> '' & pos('XCF',dominantPce) > 0 then do
  call Emit '1. Focus on JES2 XCF member/command communication.'
  call Emit '2. Review the JES2 XCF group display and MAS member states.'
  call Emit '3. Continue with XCFDIAG if the XCF layer shows a stall/failure.'
end
else if msgCount.9213 > 0 | dominantPce = 'COMM' then do
  call Emit '1. Focus on the long-running JES2 command and COMM PCE.'
  call Emit '2. Review the parsed COMMAND and PCESTAT/EVENT evidence.'
  call Emit '3. Check whether the same command repeats or overlaps with'
  call Emit '   another JES2 health-monitor symptom.'
end
else if msgCount.9203 + msgCount.9204 > 0 then do
  call Emit '1. Use the PCE name and duration to narrow the JES2 path.'
  call Emit '2. Correlate PCESTAT/EVENT with current Health Monitor state.'
  call Emit '3. Do not assume checkpoint trouble unless CKPT evidence exists.'
end
else do
  call Emit '1. No JES2 health delay is proven in the requested window.'
  call Emit '2. If the symptom is intermittent, rerun while it is active or'
  call Emit '   increase the lookback window up to 180 minutes.'
end

if cpuAvail & cpuPct >= 80 then do
  call Emit '4. CPU is currently elevated/high; use CPUDIAG to establish'
  call Emit '   whether processor pressure is sustained.'
end

if msgCount.050 + msgCount.1405 > 0 then do
  call Emit '5. Resource shortage is concurrent. Use JOBDIAG for the'
  call Emit '   actual TG/JQE/JOE/BERT/JNUM consumer analysis.'
end
return

CommandReference:
call Section 'COMMAND REFERENCE'
call Emit 'DISPLAY / DIAGNOSTIC COMMANDS COLLECTED BY THIS RUN'
shownCmd = 0
do crI = 1 to cmdLineCount
  if left(cmdLine.crI,8) = 'COMMAND:' then do
    call Emit '  'cmdLine.crI
    shownCmd = shownCmd + 1
  end
end
if shownCmd = 0 then call Emit '  No console command was collected.'

if branchCKPT then do
  call Emit ''
  call Emit 'ADDITIONAL FOCUSED CHECKPOINT DISPLAY'
  call Emit '  $D PCE(CKPT),DETAILS'
  call Emit '  ROUTE *ALL,$JD STATUS'
  call Emit '     Use ROUTE form only when the JES2 command character is'
  call Emit '     consistent across the MAS and site authority permits it.'
end

call Emit ''
call Emit 'MANUAL RECOVERY COMMANDS - NEVER EXECUTED BY JES2DIAG'
if branchCKPT then do
  call Emit '  $E CKPTLOCK,HELDBY=<failed-member>'
  call Emit '     ONLY after the lock holder is confirmed failed and the'
  call Emit '     documented checkpoint-lock recovery conditions are met.'
  call Emit '     IBM explicitly warns not to automate this command.'
  call Emit '  $E MEMBER(<failed-member>)'
  call Emit '     Requeue/restart work from a confirmed failed MAS member'
  call Emit '     when site recovery procedure requires member cleanup.'
  call Emit '  $T CKPTDEF,RECONFIG=YES'
  call Emit '     Consider only for a diagnosed checkpoint-data-set problem'
  call Emit '     under the applicable JES2/site recovery procedure.'
end
else do
  call Emit '  No generic JES2 recovery command is recommended from the'
  call Emit '  current finding. Recovery must match the proven cause.'
end
call Emit ''
call Emit 'The commands above are references, not automatic actions.'
return


RawEvidence:
call Section 'RAW DISPLAY EVIDENCE'

if cmdLineCount = 0 then do
  call Emit 'No command evidence was collected.'
  return
end

lastTag = ''
do ci = 1 to cmdLineCount
  if cmdTag.ci <> lastTag then do
    call Emit ''
    call Emit '['cmdTag.ci']'
    lastTag = cmdTag.ci
  end
  call Emit FitLine(cmdLine.ci,133)
end
return

ImportantNotes:
call Section 'IMPORTANT NOTES'
call Emit '  * JES2DIAG is read-only and issues no corrective commands.'
call Emit '  * Recovery commands shown are manual references only.'
call Emit '  * $HASP9203 alone does not prove a checkpoint problem.'
call Emit '  * Reissued alert records are not counted as distinct incidents.'
call Emit '  * CPU is a current snapshot, not a sustained-CPU conclusion.'
call Emit '  * CKPT AVGTIME is context; no universal threshold is assumed.'
call Emit '  * SYSLOG and command visibility depend on SDSF/SAF authority.'
call Emit '  * JOBDIAG remains the owner of JES2 resource-consumer triage.'
call Emit '  * XCFDIAG remains the deeper XCF/sysplex diagnostic.'
call Emit '  * The saved report remains cataloged after Browse is exited.'
return

Footer:
call Emit ''
call Emit copies('=',78)
call Emit ' JES2DIAG completed. No JES2 or system settings were changed.'
call Emit copies('=',78)
return

/*====================================================================*/
/* SAMPLE REPORT                                                      */
/*====================================================================*/
SampleReport:
call Emit ''
call Emit copies('=',78)
call Emit ' JES2DIAG - SAMPLE JES2 HEALTH / DELAY REPORT'
call KV 'Author', 'SARATH S'
call KV 'Date', DisplayDate()
call KV 'Time', startClock
call KV 'System', localSys
call KV 'Mode', 'SAMPLE - NOT LIVE SYSTEM DATA'
call KV 'Lookback shown', '30 minutes (sample)'
call Emit copies('=',78)
call Emit ''
call Emit '*** SAMPLE REPORT - VALUES BELOW DO NOT DESCRIBE THIS LPAR ***'

call Section 'TRIAGE SUMMARY'
call KV 'Current severity', 'HIGH (SAMPLE)'
call KV 'Current condition', 'JES2 CHECKPOINT DELAY (SAMPLE)'
call KV 'Finding', 'Correlated JES2 checkpoint serialization delay'
call KV 'Confidence', 'HIGH (SAMPLE INTERPRETATION)'
call KV 'Current monitor', 'ACTIVE ALERT (SAMPLE)'
call KV '$HASP9203 records', '11 (sample records, not incidents)'
call KV 'Dominant PCE', 'CKPT (9 of 11 sample records)'
call KV 'Maximum duration', '00:01:02.30 (sample)'
call KV '$HASP9207 records', '4 (sample)'
call KV '$HASP9213 records', '0 (sample)'
call KV 'CPU context', '42.5% - no sample saturation evidence'
call KV 'Resource shortage', 'NO sample $HASP050/$HASP1405'
call KV 'Likely path', 'Checkpoint serialization / MAS access'

call Section 'RECENT JES2 ALERT CORRELATION'
call KV 'SYSLOG lines read', '3,842 (sample)'
call KV 'Relevant records', '18 (sample)'
call KV 'Log completeness', 'COMPLETE (sample)'
call Emit ''
call Emit 'Message      Count   Interpretation'
call Emit copies('-',69)
call Emit '$HASP9203       11   Long PCE dispatch (sample)'
call Emit '$HASP9207        4   JES2 checkpoint lock held (sample)'
call Emit '$HASP263         1   Checkpoint access evidence (sample)'
call Emit '$HASP9302        2   Checkpoint alert cleared (sample)'
call Emit ''
call Emit 'NOTE: 11 $HASP9203 records do not mean 11 incidents. A single'
call Emit 'persistent Health Monitor condition can be reissued repeatedly.'

call Section 'CURRENT JES2 HEALTH / MAS STATE'
call KV 'Health Monitor', 'ACTIVE ALERT (sample)'
call KV 'Member rows seen', '3 (sample)'
call KV 'Failed members', '0 (sample)'
call KV 'Dormant members', '0 (sample)'
call KV 'Statuses observed', 'ACTIVE ACTIVE ACTIVE (sample)'
call KV 'JES2 XCF group', 'JES2_XCF_SAMPLE (sample)'
call KV 'XCF stall text', 'NO EXPLICIT STALL (sample)'

call Section 'PCE / COMMAND ANALYSIS'
call KV '$HASP9203 records', '11 (sample)'
call KV 'Dominant PCE', 'CKPT (sample)'
call KV 'Maximum duration', '00:01:02.30 (sample)'
call Emit ''
call Emit 'PCE        Records'
call Emit copies('-',28)
call Emit 'CKPT             9'
call Emit 'COMM             2'
call Emit ''
call Emit 'Most recent parsed sample records'
call Emit copies('-',78)
call Emit 'Record 9   Dur=00:00:41.22  PCE=CKPT'
call Emit 'Record 10  Dur=00:00:56.74  PCE=CKPT'
call Emit 'Record 11  Dur=00:01:02.30  PCE=CKPT'

call Section 'CHECKPOINT / MAS / XCF CORRELATION'
call KV '$HASP9207', '4 sample records in window'
call KV '$HASP263', '1 sample record in window'
call KV 'Last parsed lock holder', localSys' (sample scenario)'
call KV 'PERFDATA AVGHOLD', '37.4 seconds (sample)'
call KV 'PERFDATA AVGDORM', '0.8 seconds (sample)'
call KV 'Max CKPT AVGTIME', '0.092 seconds (sample)'
call KV 'JES2 XCF group', 'JES2_XCF_SAMPLE (sample)'
call KV 'Explicit XCF stall', 'NO (sample)'

call Section 'CURRENT SYSTEM CONTEXT'
call KV 'System CPU%', '42.50% (sample)'
call KV 'Page rate', '0 (sample)'
call KV 'SIO rate', '116 (sample)'
call KV 'Aux%', '1.2% (sample)'
call KV 'Real frames available', '1284500 (sample)'
call Emit ''
call Emit 'Sample CPU context does not support global CPU saturation as the'
call Emit 'primary explanation for the illustrated JES2 delay.'

call Section 'JES2 RESOURCE-PRESSURE CORRELATION'
call Emit 'No sample $HASP050 or $HASP1405 record is present.'
call Emit 'If either were present in a live run, JES2DIAG would correlate'
call Emit 'them here and direct resource-consumer analysis to JOBDIAG.'

call Section 'NEARBY SYSLOG CONTEXT'
call Emit 'The following lines are illustrative sample data:'
call Emit ''
call Emit '00:17:03 'localSys' $HASP9203 JES2 LONG PCE DISPATCH (SAMPLE)'
call Emit '          DURATION-00:01:02.30 PCE-CKPT'
call Emit '00:17:04 'localSys' $HASP9207 JES2 CHECKPOINT LOCK HELD (SAMPLE)'
call Emit '00:17:05 'localSys' $HASP263 WAITING FOR CHECKPOINT (SAMPLE)'

call Section 'ANALYSIS / INTERPRETATION'
call Emit 'In this sample, repeated long CKPT PCE dispatches occur together'
call Emit 'with checkpoint-lock evidence while CPU is not saturated.'
call Emit 'That combination makes checkpoint serialization/MAS access a'
call Emit 'stronger path than general processor pressure.'
call Emit ''
call Emit 'A real report would not make this conclusion from $HASP9203'
call Emit 'alone. JES2DIAG requires supporting checkpoint/MAS evidence.'

call Section 'RECOMMENDED DEBUGGING / POSSIBLE RESOLUTION'
call Emit '1. Review current Health Monitor and CKPT PCE detail.'
call Emit '2. Identify which MAS member holds or waits for checkpoint access.'
call Emit '3. Review checkpoint definition, performance and XCF/MAS state.'
call Emit '4. Preserve SYSLOG/dump evidence before disruptive recovery when'
call Emit '   the condition is persistent or support involvement is likely.'
call Emit '5. If a member is confirmed failed while holding the lock, follow'
call Emit '   the documented JES2/site failed-member recovery procedure.'
call Emit '6. Do not reset a checkpoint lock merely because $HASP9207 exists.'

call Section 'COMMAND REFERENCE'
call Emit 'DISPLAY / DIAGNOSTIC COMMANDS'
call Emit '  $JD JES'
call Emit '  $JD STATUS'
call Emit '  $JD DETAILS'
call Emit '  $D MEMBER(*)'
call Emit '  $D PCE(CKPT),DETAILS'
call Emit '  ROUTE *ALL,$JD STATUS'
call Emit '     Use only when JES2 command character is common across MAS.'
call Emit '  $D PERFDATA(EVENT)'
call Emit '  $D PERFDATA(PCESTAT)'
call Emit '  $D CKPTDEF'
call Emit '  $D MASDEF'
call Emit '  $D PERFDATA(CKPTSTAT)'
call Emit '  $D PERFDATA(QSUSE)'
call Emit '  D XCF,GROUP,<group-from-$D-MASDEF>'
call Emit ''
call Emit 'MANUAL RECOVERY COMMANDS - EXAMPLES, NEVER AUTO-EXECUTED'
call Emit '  $E CKPTLOCK,HELDBY=<failed-member>'
call Emit '     Use only after confirming the holder has failed and after'
call Emit '     satisfying IBM/site checkpoint-lock recovery conditions.'
call Emit '     IBM explicitly warns that this command must not be automated.'
call Emit '  $E MEMBER(<failed-member>)'
call Emit '     Used for failed-member cleanup/restart eligibility when the'
call Emit '     applicable MAS recovery procedure requires it.'
call Emit '  $T CKPTDEF,RECONFIG=YES'
call Emit '     Consider only for a diagnosed checkpoint-data-set problem'
call Emit '     and only under the applicable JES2/site procedure.'

call Section 'RAW DISPLAY EVIDENCE'
call Emit 'SAMPLE mode does not issue display commands, so there is no live'
call Emit 'raw command response. A normal run places captured command output'
call Emit 'in this section for review and escalation.'

call Section 'IMPORTANT NOTES'
call Emit '  * This is a SAMPLE report. Operational values are fabricated.'
call Emit '  * The System field is the actual LPAR name from MVSVAR.'
call Emit '  * SAMPLE mode performs no SDSF or console command execution.'
call Emit '  * Display/recovery commands are references for real incidents.'
call Emit '  * Recovery commands are never issued automatically by JES2DIAG.'
call Emit '  * $E CKPTLOCK is a high-risk exceptional recovery command.'
call Emit '  * The sample report is retained after ISPF Browse is exited.'

call Emit ''
call Emit copies('=',78)
call Emit ' JES2DIAG SAMPLE completed. No system changes were made.'
call Emit copies('=',78)
return

/*====================================================================*/
/* HELP                                                               */
/*====================================================================*/
HelpReport:
call Emit ''
call Emit copies('=',78)
call Emit ' JES2DIAG - README / HELP'
call Emit ' Author: SARATH S'
call Emit copies('=',78)
call Emit ''
call Emit 'WHAT IS JES2DIAG?'
call Emit copies('-',78)
call Emit 'JES2DIAG is a read-only JES2 health and delay diagnostic.'
call Emit 'It is intended for incidents where JES2 itself appears slow,'
call Emit 'busy, blocked or unhealthy, rather than for identifying which'
call Emit 'jobs are consuming JES2 resources.'
call Emit ''
call Emit 'WHAT DOES IT CHECK?'
call Emit copies('-',78)
call Emit '  * Recent $HASP9201-$HASP9213 Health Monitor alerts.'
call Emit '  * $HASP9203 PCE name, duration, job, exit and command details.'
call Emit '  * Repeated versus isolated alert records in the lookback window.'
call Emit '  * $HASP9207 checkpoint lock correlation and clear messages.'
call Emit '  * JES2 Health Monitor current state.'
call Emit '  * MAS member status and JES2 XCF group when applicable.'
call Emit '  * PCE and PERFDATA EVENT/PCESTAT evidence when applicable.'
call Emit '  * CKPTDEF/MASDEF/CKPTSTAT/QSUSE evidence for checkpoint paths.'
call Emit '  * Current CPU/paging/SIO context from SDSF SYS.'
call Emit '  * Concurrent $HASP050/$HASP1405 resource pressure.'
call Emit ''
call Emit 'JOBDIAG VS JES2DIAG'
call Emit copies('-',78)
call Emit 'JOBDIAG answers: Why are JES2 resources nearing exhaustion?'
call Emit 'JES2DIAG answers: Is JES2 itself delayed, blocked or unhealthy?'
call Emit ''
call Emit 'USAGE'
call Emit copies('-',78)
call Emit '  TSO JES2DIAG'
call Emit '      AUTO analysis using the previous 30 minutes.'
call Emit ''
call Emit '  TSO JES2DIAG 60'
call Emit '      AUTO analysis using the previous 60 minutes.'
call Emit ''
call Emit '  TSO JES2DIAG 9203 60'
call Emit '      Force the PCE-delay branch; still correlate other messages.'
call Emit ''
call Emit '  TSO JES2DIAG 9207 60'
call Emit '      Force the checkpoint-lock branch.'
call Emit ''
call Emit '  TSO JES2DIAG CKPT 60'
call Emit '      Force checkpoint/MAS/XCF evidence collection.'
call Emit ''
call Emit '  TSO JES2DIAG HEALTH 30'
call Emit '      Focus on current JES2 Health Monitor state.'
call Emit ''
call Emit '  TSO JES2DIAG SAMPLE'
call Emit '      Generate a retained demonstration report using sample data.'
call Emit ''
call Emit '  TSO JES2DIAG HELP'
call Emit ''
call Emit 'SAMPLE MODE'
call Emit copies('-',78)
call Emit 'SAMPLE performs no SDSF, console or JES2 diagnostic calls.'
call Emit 'Only the current system name is taken from MVSVAR(SYSNAME).'
call Emit 'Counts, timings, CPU values, members and SYSLOG text are sample.'
call Emit 'The command reference uses real diagnostic/recovery syntax, but'
call Emit 'no displayed recovery command is executed by JES2DIAG.'
call Emit ''
call Emit 'LOOKBACK'
call Emit copies('-',78)
call Emit 'Default 30 minutes. Allowed range is 5-180 minutes.'
call Emit ''
call Emit 'REPORT OUTPUT'
call Emit copies('-',78)
call Emit 'Under ISPF, JES2DIAG writes the report to a unique sequential'
call Emit 'data set and opens it in Browse. F7/F8/F9 work normally.'
call Emit 'The report data set is retained after Browse is exited.'
call Emit ''
call Emit 'SAFETY'
call Emit copies('-',78)
call Emit 'JES2DIAG issues display/monitor commands only. It never changes'
call Emit 'JES2 parameters, checkpoint ownership, XCF state or workloads.'
call Emit ''
call Emit copies('=',78)
return

/*====================================================================*/
/* REPORT SUPPORT                                                     */
/*====================================================================*/
Section:
parse arg title
call Emit ''
call Emit title
call Emit copies('-',78)
return

KV:
parse arg label, value
labelWidth = 26
line = ' 'left(label,labelWidth)' : 'value
call Emit line
return

MsgRow:
parse arg mrId, mrText
cnt = msgCount.mrId
if cnt = 0 then return
call Emit left('$HASP'mrId,12) right(cnt,6) '   'mrText
return

Emit:
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

PresentReport:
/* 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

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

uid = sysvar('SYSUID')
dqual = 'D' || substr(date('S'),3,6)
tval = time('N')
tqual = 'T' || substr(tval,1,2) || substr(tval,4,2) || ,
        substr(tval,7,2)
if reportKind = 'SAMPLE' then
  rptDsn = uid'.JES2DIAG.SAMPLE.'dqual'.'tqual
else if reportKind = 'HELP' then
  rptDsn = uid'.JES2DIAG.HELP.'dqual'.'tqual
else
  rptDsn = uid'.JES2DIAG.'dqual'.'tqual
ddn = 'J2RPT001'

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

Address TSO allocCmd
aRc = rc
if aRc <> 0 then do
  say 'JES2DIAG: Unable to allocate saved report data set.'
  do pi = 1 to rptCount
    say rpt.pi
  end
  return
end

rptCount = rptCount + 1
rpt.rptCount = ''
rptCount = rptCount + 1
rpt.rptCount = "Saved report dataset : '"rptDsn"'"
rpt.0 = rptCount

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 'JES2DIAG: Report write failed; incomplete data set deleted.'
  return
end

say "JES2DIAG report saved as '"rptDsn"'"
Address ISPEXEC "CONTROL ERRORS RETURN"
Address ISPEXEC "BROWSE DATASET('"rptDsn"')"
bRc = rc
Address ISPEXEC "CONTROL ERRORS CANCEL"

if bRc > 4 then do
  say 'JES2DIAG: ISPF Browse failed RC='bRc
  say "Saved report remains cataloged as '"rptDsn"'"
end
return

Cleanup:
if sdsfOn = 1 then do
  rc = isfcalls('OFF')
  sdsfOn = 0
end
return

/*====================================================================*/
/* HELPERS                                                            */
/*====================================================================*/
ExtractToken:
procedure
parse arg text, marker
u = translate(text)
p = pos(marker,u)
if p = 0 then return ''
rest = strip(substr(u,p + length(marker)))
if rest = '' then return ''
return CleanToken(word(rest,1))

ExtractRest:
procedure
parse arg text, marker
u = translate(text)
p = pos(marker,u)
if p = 0 then return ''
return strip(substr(text,p + length(marker)))

ExtractValue:
procedure
parse arg text, marker
u = translate(text)
p = pos(marker,u)
if p = 0 then return ''
rest = strip(substr(u,p + length(marker)))
if rest = '' then return ''
val = ''
do vi = 1 to length(rest)
  ch = substr(rest,vi,1)
  if ch = ' ' | ch = ',' then leave
  val = val || ch
end
return CleanToken(val)

CleanToken:
procedure
parse arg v
v = strip(v)
do while right(v,1) = ',' | right(v,1) = ';'
  v = left(v,length(v)-1)
end
return v

DurationSeconds:
procedure
parse arg d
v = CleanToken(d)
parse var v hh ':' mm ':' ss
if datatype(hh,'N') = 0 then return 0
if datatype(mm,'N') = 0 then return 0
if datatype(ss,'N') = 0 then return 0
return (hh * 3600) + (mm * 60) + ss

ShortageResource:
procedure
parse upper arg text
if pos('JQE',text) > 0 then return 'JQE'
if pos('JOE',text) > 0 then return 'JOE'
if pos('JNUM',text) > 0 then return 'JNUM'
if pos('BERT',text) > 0 then return 'BERT'
if pos('SPOOL',text) > 0 then return 'SPOOL'
if pos('TGS',text) > 0 | pos('TRACK',text) > 0 then return 'TG'
return 'UNKNOWN'

GetStemValue:
procedure
parse arg stemName, row, default
vname = stemName || '.' || row
if symbol(vname) = 'VAR' then return value(vname)
return default

Num:
procedure
parse arg v
v = strip(v)
if v = '' then return 0
do while pos(',',v) > 0
  v = delstr(v,pos(',',v),1)
end
if datatype(v,'N') then return v
return 0

FormatPct:
procedure
parse arg n
n = Num(n)
return strip(format(n,7,2))'%'

SecToClock:
procedure
parse arg totalSec
totalSec = trunc(totalSec)
if totalSec < 0 then totalSec = 0
if totalSec >= 86400 then totalSec = totalSec // 86400
hh = totalSec % 3600
remain = totalSec - (hh * 3600)
mm = remain % 60
ss = remain - (mm * 60)
return right('0' || hh,2) || ':' || right('0' || mm,2) || ':' || ,
       right('0' || ss,2)

SdsfDate:
procedure
parse arg baseDay
s = date('S',baseDay,'B')
yyyy = left(s,4)
mm = substr(s,5,2)
dd = substr(s,7,2)
return mm || '/' || dd || '/' || yyyy

DisplayDate:
procedure
d = date('S')
yyyy = left(d,4)
mm = substr(d,5,2) + 0
dd = substr(d,7,2)
months = 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'
mmm = word(months,mm)
return dd || '-' || mmm || '-' || yyyy

FitLine:
procedure
parse arg text, width
if width = '' then width = 133
if length(text) <= width then return text
return left(text,width)

FatalSyntax:
say
say 'JES2DIAG: 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
