/* REXX */
/*--------------------------------------------------------------------*/
/* LOGRDIAG - z/OS System Logger health diagnostic                    */
/*                                                                    */
/* Version : 1.1                                                     */
/* Author  : SARATH S                                                */
/*                                                                    */
/* Purpose : Read-only first-response diagnosis of System Logger      */
/*           directory, offload, data set and CF/logstream problems.  */
/*                                                                    */
/* Examples:                                                          */
/*   TSO LOGRDIAG                                                    */
/*   TSO LOGRDIAG LOGSTREAM SYSPLEX.OPERLOG                          */
/*   TSO LOGRDIAG SYSPLEX.OPERLOG                                    */
/*   TSO LOGRDIAG SAMPLE                                             */
/*   TSO LOGRDIAG HELP                                               */
/*                                                                    */
/* Output:                                                            */
/*   <userid>.LOGRDIAG.RPT    - persistent latest live report.         */
/*   <userid>.LOGRDIAG.SAMPLE - persistent synthetic sample report.    */
/*   <userid>.LOGRDIAG.HELP   - persistent README / HELP text.         */
/*                                                                    */
/* Safety:                                                            */
/*   Read-only. No SETLOGR, SETXCF, REPLY, FORCE, DELETE, DEFINE,     */
/*   UPDATE, allocation, catalog or policy-changing action is issued. */
/*--------------------------------------------------------------------*/

numeric digits 20
signal on syntax name FatalSyntax

/*----------------------------- Defaults ----------------------------*/
mode = 'ALL'
targetLogstream = ''
localSys = translate(MVSVAR('SYSNAME'))
uid = sysvar('SYSUID')

reportDsn = uid'.LOGRDIAG.RPT'
sampleDsn = uid'.LOGRDIAG.SAMPLE'
helpDsn = uid'.LOGRDIAG.HELP'

rpt. = ''
rpt.0 = 0
rptCount = 0

evidence. = ''
evidence.0 = 0
evidenceCount = 0

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

maxRawPerCommand = 120
maxMessageHits = 50

/*----------------------------- State -------------------------------*/
loggerActive = 'UNKNOWN'
statusCmdRc = -1
listCmdRc = -1
recallCmdRc = -1
coupleCmdRc = -1
detailCmdRc = -1
xcfStructRc = -1
grsCmdRc = -1
srRc = -1
syslogAvailable = 1

primaryCds = ''
alternateCds = ''
dsextentFormatted = ''
dsextentTotal = ''
dsextentInUse = ''
dsextentAvail = ''
dsextentUse = 'NOT DIRECTLY AVAILABLE FROM DISPLAY'

focusLogstream = ''
focusStruct = ''
focusType = 'UNKNOWN'
focusStatus = 'UNKNOWN'
focusConn = ''
stagingDsn = ''
offloadDsn = ''
requestType = ''
diagHint = ''

directoryState = 'NO SHORTAGE MESSAGE FOUND TODAY'
offloadState = 'NO DELAY EVIDENCE FOUND'
allocationState = 'NO ALLOCATION FAILURE PROVEN'
recallState = 'NO RECALL DELAY EVIDENCE FOUND'
wtorState = 'NONE FOUND'
grsState = 'NOT REQUIRED'
cfState = 'NO CF PROBLEM IDENTIFIED'

severity = 'LOW'
diagnosis = 'NO CURRENT LOGGER PROBLEM IDENTIFIED'
confidence = 'MEDIUM'

delayEvidence = 0
directoryCritical = 0
directoryWarn = 0
logstreamDirFull = 0
cfProblem = 0
lossProblem = 0
outstandingWtor = 0
grsChecked = 0

/*---------------------------- Stems --------------------------------*/
msgCount. = 0
msgLast. = ''
msgHit1. = ''
msgHit2. = ''
msgHit3. = ''

suspectCount = 0
suspectName. = ''
suspectScore. = 0
suspectReason. = ''
suspectIndex. = 0

lsCount = 0
lsName. = ''
lsStruct. = ''
lsConn. = ''
lsStatus. = ''
lsIndex. = 0

wtorCount = 0
wtorText. = ''

parse arg rawArgs
call ParseArguments rawArgs

if result <> 0 then do
  call PresentReport
  exit 12
end

if mode = 'HELP' then do
  call HelpReport
  call PresentReport
  exit 0
end

if mode = 'SAMPLE' then do
  call LoadSampleData
  call BuildStandardReport
  call PresentReport
  exit 0
end

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

isfprefix = '**'
isfowner = '*'
isfdelay = '5'

/*====================================================================*/
/* COLLECT EVIDENCE                                                   */
/*====================================================================*/

/* Current-day message history.  Each search is capped. */
call ScanMessage 'IXG257I'
call ScanMessage 'IXG261E'
call ScanMessage 'IXG262A'
call ScanMessage 'IXG271I'
call ScanMessage 'IXG278I'
call ScanMessage 'IXG281I'
call ScanMessage 'IXG301I'
call ScanMessage 'IXG310I'
call ScanMessage 'IXG311I'
call ScanMessage 'IXG320I'

/* Outstanding operator requests are stronger evidence of an active   */
/* delay than a historical message earlier in the current SYSLOG.     */
call FetchLoggerWTORs

/* System Logger availability. */
call CaptureCommand 'D LOGGER,STATUS'
statusCmdRc = result
call SaveCommandEvidence 'D LOGGER,STATUS', statusCmdRc
if statusCmdRc = 0 then call ParseLoggerStatus

/* Broad logstream health, or a requested target only. */
if mode = 'TARGET' then
  listCmd = 'D LOGGER,L,LSN='targetLogstream
else
  listCmd = 'D LOGGER,L'

call CaptureCommand listCmd
listCmdRc = result
call SaveCommandEvidence listCmd, listCmdRc
if listCmdRc = 0 then call ParseLogstreamList

/* Outstanding migrated data set recalls. */
call CaptureCommand 'D LOGGER,STATUS,RECALLS'
recallCmdRc = result
call SaveCommandEvidence 'D LOGGER,STATUS,RECALLS', recallCmdRc
if recallCmdRc = 0 then call ParseRecallDisplay

/* LOGR CDS and formatted DSEXTENT capacity. */
call CaptureCommand 'D XCF,COUPLE,TYPE=LOGR'
coupleCmdRc = result
call SaveCommandEvidence 'D XCF,COUPLE,TYPE=LOGR', coupleCmdRc
if coupleCmdRc = 0 then call ParseCoupleDisplay

/* Select one focus stream for deeper analysis. */
call SelectFocusLogstream

/* Local connection detail can expose staging/logstream information. */
if focusLogstream <> '' then do
  detailCmd = 'D LOGGER,C,LSN='focusLogstream',DETAIL'
  call CaptureCommand detailCmd
  detailCmdRc = result
  call SaveCommandEvidence detailCmd, detailCmdRc
  if detailCmdRc = 0 then call ParseConnectionDetail
end

/* For a CF stream, capture the backing CF structure status. */
if focusStruct <> '' & focusStruct <> '*DASDONLY*' then do
  xcfCmd = 'D XCF,STR,STRNAME='focusStruct
  call CaptureCommand xcfCmd
  xcfStructRc = result
  call SaveCommandEvidence xcfCmd, xcfStructRc
  if xcfStructRc = 0 then call ParseXcfStructure
end

/* When logger data set/offload delay exists, inspect logger ENQs. */
if delayEvidence then do
  grsChecked = 1
  grsCmd = 'D GRS,RES=(SYSZLOGR,*)'
  call CaptureCommand grsCmd
  grsCmdRc = result
  call SaveCommandEvidence grsCmd, grsCmdRc
  call ParseLoggerGRS
end

call ComputeAssessment

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

call BuildStandardReport
call Cleanup
call PresentReport
exit 0


/*====================================================================*/
/* ARGUMENTS                                                          */
/*====================================================================*/

ParseArguments:
parse upper arg paRaw
paRaw = strip(paRaw)

if paRaw = '' then do
  mode = 'ALL'
  return 0
end

paFirst = word(paRaw,1)

if paFirst = 'HELP' | paFirst = 'H' | paFirst = '?' then do
  mode = 'HELP'
  return 0
end

if paFirst = 'SAMPLE' then do
  mode = 'SAMPLE'
  if words(paRaw) > 1 then do
    call Emit 'LOGRDIAG: SAMPLE does not accept another parameter.'
    return 12
  end
  return 0
end

if paFirst = 'ALL' then do
  mode = 'ALL'
  if words(paRaw) > 1 then do
    call Emit 'LOGRDIAG: ALL does not accept another parameter.'
    return 12
  end
  return 0
end

if paFirst = 'LOGSTREAM' | paFirst = 'LSN' then do
  targetLogstream = word(paRaw,2)
  if targetLogstream = '' then do
    call Emit 'LOGRDIAG: LOGSTREAM requires a logstream name.'
    return 12
  end
  if words(paRaw) > 2 then do
    call Emit 'LOGRDIAG: Unexpected parameter after logstream name.'
    return 12
  end
end
else do
  /* Convenience: LOGRDIAG name is treated as LOGSTREAM name. */
  targetLogstream = paFirst
  if words(paRaw) > 1 then do
    call Emit 'LOGRDIAG: Unexpected parameter: 'word(paRaw,2)
    return 12
  end
end

targetLogstream = translate(strip(targetLogstream))

if length(targetLogstream) > 26 then do
  call Emit 'LOGRDIAG: A logstream name cannot exceed 26 characters.'
  return 12
end

mode = 'TARGET'
return 0


/*====================================================================*/
/* SYSTEM COMMAND CAPTURE                                             */
/*====================================================================*/

CaptureCommand:
parse arg ccCmd

drop ISFULOG.
cmdRaw. = ''
cmdRaw.0 = 0

isfdelay = '5'
Address SDSF "ISFSLASH '"ccCmd"'"
ccRc = rc

if ccRc <> 0 then return ccRc
if symbol('ISFULOG.0') <> 'VAR' then return 8
if isfulog.0 <= 0 then return 8

do ccI = 1 to isfulog.0
  cmdRaw.ccI = strip(isfulog.ccI)
end
cmdRaw.0 = isfulog.0
return 0


SaveCommandEvidence:
parse arg seCmd, seRc

evidenceCount = evidenceCount + 1
evidence.evidenceCount = ''
evidenceCount = evidenceCount + 1
evidence.evidenceCount = '['seCmd'] RC='seRc

if seRc <> 0 then do
  evidenceCount = evidenceCount + 1
  evidence.evidenceCount = '  Command unavailable or not authorized.'
  evidence.0 = evidenceCount
  return
end

seMax = cmdRaw.0
if seMax > maxRawPerCommand then seMax = maxRawPerCommand

do seI = 1 to seMax
  evidenceCount = evidenceCount + 1
  evidence.evidenceCount = cmdRaw.seI
end

if cmdRaw.0 > seMax then do
  evidenceCount = evidenceCount + 1
  evidence.evidenceCount = ,
    '... 'cmdRaw.0-seMax' additional response lines omitted ...'
end

evidence.0 = evidenceCount
return


/*====================================================================*/
/* SYSLOG SCAN                                                        */
/*====================================================================*/

ScanMessage:
parse upper arg smId

drop isfstartlinetoken
drop isfnextlinetoken
drop isfline.

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

smLoops = 0

do forever
  Address SDSF 'ISFLOG READ TYPE(SYSLOG)'
  smRc = rc

  if smRc <> 0 then do
    syslogAvailable = 0
    leave
  end

  smLoops = smLoops + 1
  if smLoops > maxMessageHits then leave

  if isfline.0 > 0 then do
    smLine = strip(isfline.1)
    smU = translate(smLine)

    if pos(smId,smU) > 0 then do
      msgCount.smId = msgCount.smId + 1
      msgLast.smId = smLine
      msgHit3.smId = msgHit2.smId
      msgHit2.smId = msgHit1.smId
      msgHit1.smId = smLine
      call RecordMessageContext smId, smLine
    end
  end

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

drop isffind isffindlim isfscrolltype isflinelim
drop isfstartlinetoken isfnextlinetoken
return


RecordMessageContext:
parse upper arg rmId
parse arg , rmLine

rmU = translate(rmLine)
rmLs = ExtractLogstream(rmLine)
rmDsn = ExtractDsn(rmLine)

if rmId = 'IXG261E' | rmId = 'IXG262A' then
  call ParseDsextentMessage rmLine

rmScore = 0
select
  when rmId = 'IXG301I' then rmScore = 100
  when rmId = 'IXG310I' then rmScore = 95
  when rmId = 'IXG311I' then rmScore = 95
  when rmId = 'IXG271I' then rmScore = 90
  when rmId = 'IXG257I' then rmScore = 85
  when rmId = 'IXG278I' then rmScore = 75
  when rmId = 'IXG281I' then rmScore = 75
  when rmId = 'IXG320I' then rmScore = 60
  otherwise rmScore = 40
end

if rmLs <> '' then
  call AddSuspect rmLs, rmScore, rmId' seen in current-day SYSLOG'

if rmDsn <> '' then do
  if pos('STAGING',rmU) > 0 then stagingDsn = rmDsn
  else if rmDsn <> 'LOGR' & rmDsn <> 'CDS' then offloadDsn = rmDsn
end

if rmId = 'IXG271I' | rmId = 'IXG310I' | rmId = 'IXG311I' then
  delayEvidence = 1

if pos('ALLOCATING',rmU) > 0 | pos('ALLOCATION',rmU) > 0 then
  requestType = 'ALLOCATING'
else if pos('RECALLING',rmU) > 0 | pos('MIGRATED DATASET',rmU) > 0 then
  requestType = 'RECALLING'
else if pos('MIGRATED DATA SET',rmU) > 0 then
  requestType = 'RECALLING'
else if pos('DELETING',rmU) > 0 then requestType = 'DELETING'
else if pos('UPDATING',rmU) > 0 then requestType = 'UPDATING LOGR CDS'

if pos('DIAG=',rmU) > 0 then do
  rmP = pos('DIAG=',rmU)
  diagHint = strip(substr(rmLine,rmP))
end
return


/*====================================================================*/
/* OUTSTANDING LOGGER WTORs                                           */
/*====================================================================*/

FetchLoggerWTORs:
drop REPLYID. SYSNAME. JNAME. MSGTEXT. JOBID. DATEE. TIMEE.
drop MSGTYPE. ELAPSED.

isfcols = 'REPLYID SYSNAME JNAME MSGTEXT JOBID DATEE TIMEE '
isfcols = isfcols'MSGTYPE ELAPSED'
Address SDSF 'ISFEXEC SR'
srRc = rc

if srRc <> 0 then do
  drop REPLYID. SYSNAME. JNAME. MSGTEXT. JOBID. DATEE. TIMEE.
  drop MSGTYPE.
  isfcols = 'REPLYID SYSNAME JNAME MSGTEXT JOBID DATEE TIMEE MSGTYPE'
  Address SDSF 'ISFEXEC SR'
  srRc = rc
end

if srRc <> 0 then return

do fwI = 1 to isfrows
  fwText = GetVar('MSGTEXT',fwI,'')
  fwU = translate(fwText)

  if pos('IXG272E',fwU) = 0 & pos('IXG312E',fwU) = 0 then iterate

  wtorCount = wtorCount + 1
  fwReply = GetVar('REPLYID',fwI,'')
  fwSys = GetVar('SYSNAME',fwI,'')
  fwAge = GetVar('ELAPSED',fwI,'')

  fwLine = 'Reply='fwReply
  if fwSys <> '' then fwLine = fwLine' System='fwSys
  if fwAge <> '' then fwLine = fwLine' Age='fwAge
  fwLine = fwLine' 'fwText
  wtorText.wtorCount = fwLine

  fwLs = ExtractLogstream(fwText)
  if fwLs <> '' then
    call AddSuspect fwLs, 110, 'Outstanding logger WTOR'

  outstandingWtor = 1
  delayEvidence = 1
end
return


/*====================================================================*/
/* LOGGER STATUS                                                      */
/*====================================================================*/

ParseLoggerStatus:
loggerActive = 'NO / NOT CONFIRMED'

do psI = 1 to cmdRaw.0
  psU = translate(strip(cmdRaw.psI))
  if words(psU) > 0 then do
    if word(psU,words(psU)) = 'ACTIVE' then do
      loggerActive = 'YES'
      leave
    end
  end
end
return


/*====================================================================*/
/* LOGSTREAM TABLE                                                    */
/*====================================================================*/

ParseLogstreamList:
do plI = 1 to cmdRaw.0
  plLine = strip(cmdRaw.plI)
  plStatus = StatusFromLine(plLine)
  if plStatus = '' then iterate

  plU = translate(plLine)
  plP = pos(plStatus,plU)
  if plP <= 1 then iterate

  plPrefix = strip(substr(plLine,1,plP-1))
  plName = translate(word(plPrefix,1))
  plStruct = translate(word(plPrefix,2))
  plConn = word(plPrefix,3)

  if plName = '' | plName = 'LOGSTREAM' then iterate
  if plName = 'SYSTEM' | plName = 'NUMBER' then iterate

  call AddLogstream plName, plStruct, plConn, plStatus

  if ProblemStatus(plStatus) then do
    plScore = StatusScore(plStatus)
    call AddSuspect plName, plScore, 'D LOGGER status 'plStatus
  end
end
return


AddLogstream:
parse upper arg alName, alStruct, alConn
parse arg , , , alStatus

alKey = c2x(alName)
alIdx = lsIndex.alKey

if alIdx = 0 then do
  lsCount = lsCount + 1
  alIdx = lsCount
  lsIndex.alKey = alIdx
  lsName.alIdx = alName
end

lsStruct.alIdx = alStruct
lsConn.alIdx = alConn
lsStatus.alIdx = alStatus

if alStatus = 'DS DIRECTORY FULL' then logstreamDirFull = 1

if alStatus = 'STRUCTURE FULL' | ,
   alStatus = 'REBUILD FAILED' | ,
   alStatus = 'LOST CONNECTIVITY' | ,
   alStatus = 'REBUILD IN PROGRESS' | ,
   alStatus = 'DUPLEXING REBUILD' then cfProblem = 1

if alStatus = 'LOSS OF DATA' then lossProblem = 1
return


/*====================================================================*/
/* RECALL DISPLAY                                                     */
/*====================================================================*/

ParseRecallDisplay:
prFound = 0

do prI = 1 to cmdRaw.0
  prU = translate(cmdRaw.prI)

  if pos('RECALLING',prU) > 0 | ,
     pos('MIGRATED DATASET',prU) > 0 | ,
     pos('MIGRATED DATA SET',prU) > 0 then do
    prFound = 1
    leave
  end
end

if prFound then recallState = 'RECALL ACTIVITY / DELAY DISPLAYED'
else recallState = 'NO RECALL ACTIVITY IDENTIFIED IN DISPLAY'
return


/*====================================================================*/
/* LOGR COUPLE DATA SET                                               */
/*====================================================================*/

ParseCoupleDisplay:
do pcI = 1 to cmdRaw.0
  pcLine = strip(cmdRaw.pcI)
  pcU = translate(pcLine)

  pcP = pos('PRIMARY DSN:',pcU)
  if pcP > 0 then do
    pcX = strip(substr(pcLine,pcP+length('PRIMARY DSN:')))
    if pcX <> '' then primaryCds = word(pcX,1)
  end

  pcP = pos('ALTERNATE DSN:',pcU)
  if pcP > 0 then do
    pcX = strip(substr(pcLine,pcP+length('ALTERNATE DSN:')))
    if pcX <> '' then alternateCds = word(pcX,1)
  end

  pcP = pos('DSEXTENT(',pcU)
  if pcP > 0 then do
    pcX = substr(pcU,pcP+length('DSEXTENT('))
    pcQ = pos(')',pcX)
    if pcQ > 1 then dsextentFormatted = substr(pcX,1,pcQ-1)
  end
end
return


/*====================================================================*/
/* FOCUS SELECTION                                                    */
/*====================================================================*/

SelectFocusLogstream:
if mode = 'TARGET' then do
  focusLogstream = targetLogstream
end
else do
  sfBest = 0
  sfScore = -1

  do sfI = 1 to suspectCount
    if suspectScore.sfI > sfScore then do
      sfBest = sfI
      sfScore = suspectScore.sfI
    end
  end

  if sfBest > 0 then focusLogstream = suspectName.sfBest
end

if focusLogstream = '' then return

sfKey = c2x(focusLogstream)
sfIdx = lsIndex.sfKey

if sfIdx > 0 then do
  focusStruct = lsStruct.sfIdx
  focusStatus = lsStatus.sfIdx
  focusConn = lsConn.sfIdx

  if focusStruct = '*DASDONLY*' then focusType = 'DASD-ONLY'
  else if focusStruct <> '' then focusType = 'CF'
end
return


/*====================================================================*/
/* CONNECTION DETAIL                                                  */
/*====================================================================*/

ParseConnectionDetail:
do pdI = 1 to cmdRaw.0
  pdLine = strip(cmdRaw.pdI)
  pdU = translate(pdLine)

  if stagingDsn = '' then do
    if pos('STAGING',pdU) > 0 & pos('DSN=',pdU) > 0 then
      stagingDsn = ExtractDsn(pdLine)
  end

  if offloadDsn = '' then do
    if pos('OFFLOAD',pdU) > 0 & pos('DSN=',pdU) > 0 then
      offloadDsn = ExtractDsn(pdLine)
  end

  if focusStruct = '' then do
    pdP = pos('STRUCTURE:',pdU)
    if pdP > 0 then do
      pdX = strip(substr(pdLine,pdP+length('STRUCTURE:')))
      pdX = CleanToken(word(pdX,1))
      if pdX <> '' then focusStruct = translate(pdX)
    end
  end
end

if focusStruct = '*DASDONLY*' then focusType = 'DASD-ONLY'
else if focusStruct <> '' then focusType = 'CF'
return


/*====================================================================*/
/* CF STRUCTURE                                                       */
/*====================================================================*/

ParseXcfStructure:
cfState = 'CF STRUCTURE DISPLAY CAPTURED'

do pxI = 1 to cmdRaw.0
  pxU = translate(cmdRaw.pxI)

  if pos('NOT ALLOCATED',pxU) > 0 then do
    cfState = 'STRUCTURE NOT ALLOCATED'
    cfProblem = 1
  end

  if pos('FAILED',pxU) > 0 | pos('FAILURE',pxU) > 0 then do
    cfState = 'FAILURE TEXT PRESENT - REVIEW RAW XCF DISPLAY'
    cfProblem = 1
  end
end
return


/*====================================================================*/
/* LOGGER GRS                                                         */
/*====================================================================*/

ParseLoggerGRS:
if grsChecked = 0 then return

if grsCmdRc <> 0 then do
  grsState = 'UNAVAILABLE / NOT AUTHORIZED'
  return
end

pgFound = 0

do pgI = 1 to cmdRaw.0
  pgU = translate(cmdRaw.pgI)

  if pos('SYSZLOGR',pgU) > 0 then do
    if pos('NO RESOURCE',pgU) = 0 & pos('DISPLAY GRS',pgU) = 0 then
      pgFound = 1
  end
end

if pgFound then
  grsState = 'SYSZLOGR RESOURCE EVIDENCE RETURNED - REVIEW RAW DISPLAY'
else
  grsState = 'NO SYSZLOGR RESOURCE ROW IDENTIFIED'
return


/*====================================================================*/
/* ASSESSMENT                                                         */
/*====================================================================*/

ComputeAssessment:
/* Current-day message evidence. */
if msgCount.IXG262A > 0 then do
  directoryCritical = 1
  directoryState = 'CRITICAL DSEXTENT SHORTAGE REPORTED TODAY'
  if dsextentTotal <> '' & dsextentInUse <> '' then
    dsextentUse = dsextentInUse' / 'dsextentTotal' IN USE'
  else
    dsextentUse = '>95% CRITICAL THRESHOLD WAS CROSSED TODAY'
end
else if msgCount.IXG261E > 0 then do
  directoryWarn = 1
  directoryState = 'DSEXTENT SHORTAGE REPORTED TODAY'
  if dsextentTotal <> '' & dsextentInUse <> '' then
    dsextentUse = dsextentInUse' / 'dsextentTotal' IN USE'
  else
    dsextentUse = '>85% SHORTAGE THRESHOLD WAS CROSSED TODAY'
end
else if msgCount.IXG257I > 0 then do
  directoryWarn = 1
  directoryState = 'LOGSTREAM DIRECTORY >90% REPORTED TODAY'
end

if logstreamDirFull then
  directoryState = 'AT LEAST ONE LOGSTREAM STATUS IS DS DIRECTORY FULL'

/* Offload/data set request delay. */
if msgCount.IXG271I > 0 | msgCount.IXG310I > 0 | ,
   msgCount.IXG311I > 0 then delayEvidence = 1

if outstandingWtor then delayEvidence = 1

if outstandingWtor then do
  offloadState = 'ACTIVE LOGGER DELAY - WTOR OUTSTANDING'
  wtorState = wtorCount' LOGGER WTOR(S) OUTSTANDING'
end
else if msgCount.IXG311I > 0 then
  offloadState = 'SECONDARY OFFLOAD DELAY REPORTED TODAY'
else if msgCount.IXG310I > 0 then
  offloadState = 'OFFLOAD NOT PROGRESSING REPORTED TODAY'
else if msgCount.IXG271I > 0 then
  offloadState = 'LOGGER DATA SET REQUEST DELAY REPORTED TODAY'
else if focusStatus = 'OFFLOAD IN PROGRESS' then
  offloadState = 'OFFLOAD IN PROGRESS'

if requestType = 'ALLOCATING' then
  allocationState = 'LOGGER ALLOCATION REQUEST DELAY IDENTIFIED'
else if msgCount.IXG301I > 0 then
  allocationState = 'OFFLOAD FAILURE SEEN - REVIEW IXG301I EVIDENCE'

if requestType = 'RECALLING' then
  recallState = 'RECALLING REQUEST IDENTIFIED IN LOGGER MESSAGE'

/* Priority is deliberately evidence based, not guessed. */
if loggerActive = 'NO / NOT CONFIRMED' & statusCmdRc = 0 then do
  severity = 'HIGH'
  diagnosis = 'SYSTEM LOGGER NOT ACTIVE / NOT CONFIRMED ACTIVE'
  confidence = 'HIGH'
end
else if lossProblem then do
  severity = 'HIGH'
  diagnosis = 'LOGSTREAM LOSS OF DATA CONDITION'
  confidence = 'HIGH'
end
else if logstreamDirFull then do
  severity = 'HIGH'
  diagnosis = 'LOGSTREAM DATA SET DIRECTORY FULL'
  confidence = 'HIGH'
end
else if directoryCritical then do
  severity = 'HIGH'
  diagnosis = 'GLOBAL LOGGER DIRECTORY EXTENT SHORTAGE'
  confidence = 'HIGH'
end
else if cfProblem then do
  severity = 'HIGH'
  diagnosis = 'CF / LOGSTREAM AVAILABILITY PROBLEM'
  confidence = 'HIGH'
end
else if outstandingWtor then do
  severity = 'HIGH'
  diagnosis = 'LOGGER DATA SET / OFFLOAD REQUEST STALLED'
  confidence = 'HIGH'
end
else if msgCount.IXG301I > 0 then do
  severity = 'HIGH'
  diagnosis = 'LOGGER OFFLOAD FAILURE REPORTED TODAY'
  confidence = 'HIGH'
end
else if delayEvidence then do
  severity = 'MEDIUM'
  diagnosis = 'LOGGER DATA SET / OFFLOAD DELAY REPORTED TODAY'
  confidence = 'MEDIUM'
end
else if directoryWarn then do
  severity = 'MEDIUM'
  diagnosis = 'LOGGER DIRECTORY CAPACITY WARNING'
  confidence = 'HIGH'
end
else if loggerActive = 'YES' then do
  severity = 'LOW'
  diagnosis = 'NO ACTIVE LOGGER FAILURE IDENTIFIED BY THIS RUN'
  confidence = 'MEDIUM'
end
else do
  severity = 'UNKNOWN'
  diagnosis = 'LOGGER HEALTH COULD NOT BE FULLY DETERMINED'
  confidence = 'LOW'
end

if focusType = 'UNKNOWN' & focusStruct = '*DASDONLY*' then
  focusType = 'DASD-ONLY'
return


/*====================================================================*/
/* STANDARD REPORT BUILDER / SAMPLE DATA                              */
/*====================================================================*/

BuildStandardReport:
call Header
call TriageSummary
call PriorityFindings
call FocusDetails
call RootCauseMatrix
call LogstreamHealth
call MessageHistory
call OutstandingRequests
call RawEvidence
call CommandReference
call Recommendations
call Notes
call Footer
return


LoadSampleData:
/*--------------------------------------------------------------------*/
/* SAMPLE mode is deliberately synthetic. No SDSF or console command  */
/* is issued. The executing system/date/time remain real so users can  */
/* see exactly how a normal report is presented on their LPAR.         */
/*--------------------------------------------------------------------*/

loggerActive = 'YES'
statusCmdRc = 0
listCmdRc = 0
recallCmdRc = 0
coupleCmdRc = 0
detailCmdRc = 0
xcfStructRc = 0
grsCmdRc = 0
srRc = 0
syslogAvailable = 1

primaryCds = 'SYS1.LOGR.CDS01'
alternateCds = 'SYS1.LOGR.CDS02'
dsextentFormatted = '5'
dsextentTotal = '5'
dsextentInUse = '5'
dsextentAvail = '0'
dsextentUse = '5 / 5 IN USE'

focusLogstream = 'SAMPLE.LOGSTREAM.A'
focusStruct = 'LOGGERSTR1'
focusType = 'CF'
focusStatus = 'OFFLOAD IN PROGRESS'
focusConn = '000001'
stagingDsn = 'SYS1.SAMPLE.LOGR.STG01'
offloadDsn = 'SYS1.SAMPLE.LOGR.A0000001'
requestType = 'ALLOCATING'
diagHint = 'DIAG=ALLOCATION DELAY - SAMPLE DATA'

directoryState = 'CRITICAL DSEXTENT SHORTAGE - SAMPLE'
offloadState = 'LOGGER DATA SET ALLOCATION DELAY - SAMPLE'
allocationState = 'ALLOCATION DELAY PRESENT - CAUSE NOT YET PROVEN'
recallState = 'NO RECALL DELAY IN THIS SAMPLE'
wtorState = '1 SAMPLE LOGGER WTOR'
grsState = 'NO SYSZLOGR BLOCKER IN SAMPLE'
cfState = 'CF STRUCTURE AVAILABLE - SAMPLE'

severity = 'HIGH'
diagnosis = 'GLOBAL LOGGER DIRECTORY EXTENT SHORTAGE'
confidence = 'HIGH'

delayEvidence = 1
directoryCritical = 1
directoryWarn = 0
logstreamDirFull = 0
cfProblem = 0
lossProblem = 0
outstandingWtor = 1
grsChecked = 1

msgCount. = 0
msgLast. = ''
msgHit1. = ''
msgHit2. = ''
msgHit3. = ''

msgCount.IXG262A = 1
msgHit1.IXG262A = ,
  'IXG262A CRITICAL SHORTAGE OF DIRECTORY EXTENT RECORDS ' ,
  'TOTAL: 5 IN-USE: 5 AVAILABLE: 0'

msgCount.IXG271I = 1
msgHit1.IXG271I = ,
  'IXG271I LOGGER DATA SET REQUEST IN PRODUCTION ALLOCATION ' ,
  'FOR LOGSTREAM SAMPLE.LOGSTREAM.A'

suspectCount = 1
suspectName.1 = focusLogstream
suspectScore.1 = 110
suspectReason.1 = 'Synthetic logger WTOR / allocation delay'
sampleKey = c2x(focusLogstream)
suspectIndex.sampleKey = 1

lsCount = 3
lsName.1 = 'SAMPLE.LOGSTREAM.A'
lsStruct.1 = 'LOGGERSTR1'
lsConn.1 = '000001'
lsStatus.1 = 'OFFLOAD IN PROGRESS'
sampleKey = c2x(lsName.1)
lsIndex.sampleKey = 1

lsName.2 = 'SAMPLE.LOGSTREAM.B'
lsStruct.2 = '*DASDONLY*'
lsConn.2 = '000000'
lsStatus.2 = 'AVAILABLE'
sampleKey = c2x(lsName.2)
lsIndex.sampleKey = 2

lsName.3 = 'SAMPLE.LOGSTREAM.C'
lsStruct.3 = 'LOGGERSTR2'
lsConn.3 = '000002'
lsStatus.3 = 'IN USE'
sampleKey = c2x(lsName.3)
lsIndex.sampleKey = 3

wtorCount = 1
wtorText.1 = ,
  'Reply=17 System='localSys ,
  ' IXG272E LOGGER DATA SET REQUEST DELAYED - SAMPLE ONLY'

evidenceCount = 0
call AddSampleEvidence '[D LOGGER,STATUS] RC=0'
call AddSampleEvidence 'IXG601I SYSTEM LOGGER STATUS'
call AddSampleEvidence 'SYSTEM   SYSTEM LOGGER STATUS'
call AddSampleEvidence localSys'      ACTIVE'
call AddSampleEvidence ''
call AddSampleEvidence '[D LOGGER,L] RC=0'
call AddSampleEvidence ,
  'SAMPLE.LOGSTREAM.A LOGGERSTR1 000001 OFFLOAD IN PROGRESS'
call AddSampleEvidence ,
  'SAMPLE.LOGSTREAM.B *DASDONLY* 000000 AVAILABLE'
call AddSampleEvidence ''
call AddSampleEvidence '[D LOGGER,STATUS,RECALLS] RC=0'
call AddSampleEvidence 'NO OUTSTANDING LOGGER RECALLS - SAMPLE'
call AddSampleEvidence ''
call AddSampleEvidence '[D XCF,COUPLE,TYPE=LOGR] RC=0'
call AddSampleEvidence 'PRIMARY DSN: SYS1.LOGR.CDS01'
call AddSampleEvidence 'ALTERNATE DSN: SYS1.LOGR.CDS02'
call AddSampleEvidence 'LOGR DSEXTENT(5)'
call AddSampleEvidence ''
call AddSampleEvidence ,
  '[D LOGGER,C,LSN=SAMPLE.LOGSTREAM.A,DETAIL] RC=0'
call AddSampleEvidence 'STRUCTURE: LOGGERSTR1'
call AddSampleEvidence 'STAGING DSN=SYS1.SAMPLE.LOGR.STG01'
call AddSampleEvidence 'OFFLOAD DSN=SYS1.SAMPLE.LOGR.A0000001'
call AddSampleEvidence ''
call AddSampleEvidence '[D XCF,STR,STRNAME=LOGGERSTR1] RC=0'
call AddSampleEvidence 'STRUCTURE LOGGERSTR1 IS ALLOCATED - SAMPLE'
call AddSampleEvidence ''
call AddSampleEvidence '[D GRS,RES=(SYSZLOGR,*)] RC=0'
call AddSampleEvidence 'NO BLOCKING SYSZLOGR RESOURCE - SAMPLE'
evidence.0 = evidenceCount
return


AddSampleEvidence:
parse arg aseText
evidenceCount = evidenceCount + 1
evidence.evidenceCount = aseText
evidence.0 = evidenceCount
return


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

Header:
call Emit ''
call Emit copies('=',78)
call Emit ' LOGRDIAG - SYSTEM LOGGER FIRST-RESPONSE DIAGNOSTIC'
call KV 'Author', 'SARATH S'
call KV 'Date', DisplayDate()
call KV 'Time', time('N')
call KV 'System', localSys
call KV 'Mode', mode
if mode = 'TARGET' then call KV 'Requested logstream', targetLogstream
if mode = 'SAMPLE' then do
  call KV 'Data source', 'SYNTHETIC SAMPLE - NOT LIVE LOGGER DATA'
end
call Emit copies('=',78)
if mode = 'SAMPLE' then do
  call Emit ' *** SAMPLE REPORT - NO LIVE LOGGER COMMANDS WERE ISSUED ***'
  call Emit ' *** VALUES BELOW ARE FOR DEMONSTRATION / TRAINING ONLY  ***'
end
return


TriageSummary:
call Section 'TRIAGE SUMMARY'
call KV 'Current severity', severity
call KV 'Current condition', diagnosis
call KV 'Assessment confidence', confidence
call KV 'System Logger active', loggerActive
call KV 'Focus logstream', ValueOr(focusLogstream,'NOT IDENTIFIED')
call KV 'Type', focusType
call KV 'Structure', ValueOr(focusStruct,'NOT IDENTIFIED')
call KV 'Logstream status', focusStatus
call KV 'Connections shown', ValueOr(focusConn,'N/A')
call KV 'Offload condition', offloadState
call KV 'Allocation failure evidence', allocationState
call KV 'Directory condition', directoryState
call KV 'Formatted DSEXTENT records', ,
        ValueOr(dsextentFormatted,'NOT AVAILABLE')
call KV 'DSEXTENT in use / total', dsextentUse
call KV 'DSEXTENT available', ValueOr(dsextentAvail,'NOT AVAILABLE')
call KV 'Staging data set', ValueOr(stagingDsn,'NOT IDENTIFIED')
call KV 'Offload data set', ValueOr(offloadDsn,'NOT IDENTIFIED')
call KV 'Outstanding logger WTORs', wtorState
call KV 'Recall condition', recallState
call KV 'Logger GRS check', grsState
return


PriorityFindings:
call Section 'PRIORITY FINDINGS'

pfN = 0

if lossProblem then do
  pfN = pfN + 1
  call Emit pfN'. [HIGH] LOSS OF DATA is reported for a logstream.'
end

if logstreamDirFull then do
  pfN = pfN + 1
  call Emit pfN'. [HIGH] A logstream data set directory is full.'
end

if directoryCritical then do
  pfN = pfN + 1
  call Emit pfN'. [HIGH] Global LOGR DSEXTENT capacity is critical.'
  if dsextentTotal <> '' then do
    call Emit '   Directory extents: 'dsextentInUse' in use of ' ,
              dsextentTotal', available='dsextentAvail'.'
  end
end
else if directoryWarn then do
  pfN = pfN + 1
  call Emit pfN'. [MEDIUM] LOGR directory capacity warning observed.'
end

if outstandingWtor then do
  pfN = pfN + 1
  call Emit pfN'. [HIGH] A System Logger delay WTOR is outstanding.'
end
else if delayEvidence then do
  pfN = pfN + 1
  call Emit pfN'. [MEDIUM] Logger data set/offload delay was observed.'
end

if msgCount.IXG301I > 0 then do
  pfN = pfN + 1
  call Emit pfN'. [HIGH] An offload failure message was observed.'
end

if cfProblem then do
  pfN = pfN + 1
  call Emit pfN'. [HIGH] CF/logstream availability evidence is abnormal.'
end

if requestType = 'ALLOCATING' then do
  pfN = pfN + 1
  call Emit pfN'. [INFO] Allocation delay exists, but DASD-full/SMS'
  call Emit '   root cause is not proven by Logger evidence alone.'
end

if syslogAvailable = 0 then do
  pfN = pfN + 1
  call Emit pfN'. [INFO] SYSLOG evidence is incomplete/unavailable.'
end

if pfN = 0 then do
  call Emit '1. [INFO] No active System Logger failure was identified.'
end

if mode = 'SAMPLE' then do
  call Emit ''
  call Emit 'Sample interpretation: the primary demonstrated fault is'
  call Emit 'global LOGR directory-extent exhaustion. The allocation'
  call Emit 'delay is supporting evidence, not proof that DASD is full.'
end
return


RootCauseMatrix:
call Section 'ROOT-CAUSE DIFFERENTIATION'

if directoryCritical then
  r1 = 'EVIDENCE - IXG262A SEEN TODAY'
else if directoryWarn then
  r1 = 'WARNING EVIDENCE - IXG261E / IXG257I SEEN TODAY'
else
  r1 = 'NO SHORTAGE MESSAGE FOUND TODAY'

if logstreamDirFull then
  r2 = 'CONFIRMED - D LOGGER STATUS DS DIRECTORY FULL'
else
  r2 = 'NOT IDENTIFIED IN CURRENT D LOGGER OUTPUT'

if outstandingWtor then
  r3 = 'ACTIVE - LOGGER DELAY WTOR OUTSTANDING'
else if delayEvidence then
  r3 = 'REPORTED TODAY - NO LOGGER WTOR FOUND'
else
  r3 = 'NO DELAY EVIDENCE FOUND'

if requestType = 'ALLOCATING' then
  r4 = 'UNRESOLVED - ALLOCATION DELAY IS NOT DASD-FULL PROOF'
else
  r4 = 'NOT PROVEN BY CURRENT LOGGER EVIDENCE'

if requestType = 'ALLOCATING' then
  r5 = 'UNRESOLVED - REVIEW SMS / ALLOCATION MESSAGES'
else
  r5 = 'NOT PROVEN BY CURRENT LOGGER EVIDENCE'

if cfProblem then
  r6 = 'EVIDENCE - ABNORMAL CF / LOGSTREAM STATUS'
else if focusType = 'CF' then
  r6 = 'NO ABNORMAL CF STATUS IDENTIFIED'
else if focusType = 'DASD-ONLY' then
  r6 = 'NOT APPLICABLE - DASD-ONLY LOGSTREAM'
else
  r6 = 'UNKNOWN'

call KV 'Global DSEXTENT shortage', r1
call KV 'Logstream directory full', r2
call KV 'Offload / data set delay', r3
call KV 'DASD capacity problem', r4
call KV 'SMS / storage-group problem', r5
call KV 'CF / logstream problem', r6
call Emit ''
call Emit 'Important: DASD capacity and SMS/storage-group causes are'
call Emit 'not asserted unless the collected logger evidence proves them.'
call Emit 'An allocation delay alone does not prove that DASD is full.'
return


FocusDetails:
call Section 'SYSTEM LOGGER / FOCUS LOGSTREAM CONTEXT'

if focusLogstream = '' then do
  call KV 'Primary LOGR CDS', ValueOr(primaryCds,'NOT IDENTIFIED')
  call KV 'Alternate LOGR CDS', ValueOr(alternateCds,'NOT IDENTIFIED')
  call KV 'Formatted DSEXTENT', ,
          ValueOr(dsextentFormatted,'NOT AVAILABLE')
  call Emit ''
  call Emit 'No specific affected logstream could be identified.'
  if directoryCritical | directoryWarn then do
    call Emit 'The observed directory-extent condition can be global to'
    call Emit 'the active LOGR couple data set rather than one stream.'
  end
  return
end

call KV 'Primary LOGR CDS', ValueOr(primaryCds,'NOT IDENTIFIED')
call KV 'Alternate LOGR CDS', ValueOr(alternateCds,'NOT IDENTIFIED')
call KV 'Formatted DSEXTENT', ,
        ValueOr(dsextentFormatted,'NOT AVAILABLE')
call Emit ''
call KV 'Logstream', focusLogstream
call KV 'Type', focusType
call KV 'Structure', ValueOr(focusStruct,'NOT IDENTIFIED')
call KV 'Status', focusStatus
call KV 'Connections', ValueOr(focusConn,'N/A')
call KV 'Staging data set', ValueOr(stagingDsn,'NOT IDENTIFIED')
call KV 'Offload data set', ValueOr(offloadDsn,'NOT IDENTIFIED')
call KV 'Observed request type', ValueOr(requestType,'NOT IDENTIFIED')
call KV 'Logger diagnostic text', ValueOr(diagHint,'NOT IDENTIFIED')
call KV 'Connection detail RC', detailCmdRc

if focusType = 'CF' then do
  call KV 'CF structure display RC', xcfStructRc
  call KV 'CF structure assessment', cfState
end
return


LogstreamHealth:
call Section 'LOGSTREAM HEALTH'

if lsCount = 0 then do
  call Emit 'No logstream table rows were parsed from D LOGGER,L.'
  call Emit 'Review RAW DISPLAY EVIDENCE for authorization or format'
  call Emit 'differences on this z/OS level.'
  return
end

call Emit 'Logstream                  Structure         Conn Status'
call Emit copies('-',74)

lhShown = 0
do lhI = 1 to lsCount
  lhSt = lsStatus.lhI

  if mode = 'ALL' then do
    if ProblemStatus(lhSt) = 0 & lhSt <> 'OFFLOAD IN PROGRESS' then
      iterate
  end

  lhLine = left(lsName.lhI,26)' 'left(lsStruct.lhI,16)' '
  lhLine = lhLine||right(ValueOr(lsConn.lhI,'-'),4)' 'lhSt
  call Emit lhLine
  lhShown = lhShown + 1
end

if lhShown = 0 then do
  if mode = 'ALL' then
    call Emit 'No abnormal logstream status was identified.'
  else
    call Emit 'No parsed row matched the requested target.'
end
return


MessageHistory:
call Section 'CURRENT-DAY LOGGER MESSAGE HISTORY'

if syslogAvailable = 0 then do
  call Emit 'Current-day SDSF SYSLOG search was unavailable or not'
  call Emit 'authorized. Message-history conclusions are incomplete.'
  return
end

mhIds = 'IXG257I IXG261E IXG262A IXG271I IXG278I IXG281I '
mhIds = mhIds'IXG301I IXG310I IXG311I IXG320I'

mhAny = 0
do mhI = 1 to words(mhIds)
  mhId = word(mhIds,mhI)
  mhCnt = msgCount.mhId

  if mhCnt = 0 then iterate
  mhAny = 1

  call Emit ''
  call KV mhId' matches', mhCnt
  if msgHit1.mhId <> '' then call Emit '  Latest: 'msgHit1.mhId
  if msgHit2.mhId <> '' then call Emit '  Prior : 'msgHit2.mhId
  if msgHit3.mhId <> '' then call Emit '  Prior : 'msgHit3.mhId

  if mhCnt >= maxMessageHits then
    call Emit '  Note  : search cap reached; actual count may be higher.'
end

if mhAny = 0 then
  call Emit 'No selected logger shortage/offload messages were found.'
return


OutstandingRequests:
call Section 'OUTSTANDING LOGGER OPERATOR REQUESTS'

if srRc <> 0 then do
  call Emit 'SDSF SR was unavailable or not authorized. RC='srRc
  return
end

if wtorCount = 0 then do
  call Emit 'No IXG272E or IXG312E request is currently visible in SR.'
  return
end

do orI = 1 to wtorCount
  call Emit wtorText.orI
end
return


RawEvidence:
call Section 'RAW DISPLAY EVIDENCE'

if evidenceCount = 0 then do
  call Emit 'No command evidence was captured.'
  return
end

do reI = 1 to evidenceCount
  call Emit evidence.reI
end
return


CommandReference:
call Section 'COMMAND REFERENCE'

call Emit 'DISPLAY / DIAGNOSTIC COMMANDS'
call Emit '  Automatically collected when applicable:'
call Emit ''
call Emit '    D LOGGER,STATUS'
call Emit '    D LOGGER,L'
call Emit '    D LOGGER,STATUS,RECALLS'
call Emit '    D XCF,COUPLE,TYPE=LOGR'

crLs = focusLogstream
if crLs = '' then crLs = '<logstream>'
crSt = focusStruct
if crSt = '' | crSt = '*DASDONLY*' then crSt = '<structure>'

call Emit '    D LOGGER,L,LSN='crLs
call Emit '    D LOGGER,C,LSN='crLs',DETAIL'
call Emit '    D XCF,STR,STRNAME='crSt
call Emit '    D GRS,RES=(SYSZLOGR,*)'
call Emit ''
call Emit '  Useful additional manual display:'
call Emit '    D LOGGER,IXGCNF,MANAGE'
call Emit ''
call Emit 'MANUAL RECOVERY / CHANGE COMMANDS'
call Emit '  LOGRDIAG NEVER issues the commands below. They are shown so'
call Emit '  a user knows what controlled recovery might involve.'
call Emit ''
call Emit '  Directory extent shortage:'
call Emit '    SETXCF COUPLE,TYPE=LOGR,PSWITCH'
call Emit '      Use only after an alternate LOGR CDS with adequate'
call Emit '      DSEXTENT capacity has been verified and site change'
call Emit '      procedures authorize the switch.'
call Emit ''
call Emit '  Stalled migrated-data-set recall:'
call Emit '    SETLOGR FORCE,NORECALL'
call Emit '      Consider only when D LOGGER,STATUS,RECALLS confirms the'
call Emit '      relevant recall problem and site procedure permits it.'
call Emit ''
call Emit '  LOGR policy changes / new CDS formatting normally require'
call Emit '  IXCMIAPU / IXCL1DSU work under normal change control;'
call Emit '  LOGRDIAG does not generate or submit that change automatically.'
return


Recommendations:
call Section 'RECOMMENDED DEBUGGING / POSSIBLE RESOLUTION'

if diagnosis = 'GLOBAL LOGGER DIRECTORY EXTENT SHORTAGE' | ,
   directoryCritical | directoryWarn then do
  call Emit '1. Treat the DSEXTENT shortage separately from DASD free'
  call Emit '   space. It is a LOGR directory-record capacity problem.'
  call Emit '2. Review the active primary and alternate LOGR CDS shown'
  call Emit '   above and confirm both were formatted with adequate'
  call Emit '   DSEXTENT capacity.'
  call Emit '3. Use IXCMIAPU LIST LOGSTREAM NAME(*) DETAIL(YES) under'
  call Emit '   your normal site procedure to identify logstreams using'
  call Emit '   large numbers of offload data sets/directory extents.'
  call Emit '4. Do not have LOGRDIAG automatically switch/reformat a CDS.'
  call Emit '   Any LOGR CDS replacement or switch is a planned change.'
end
else if logstreamDirFull then do
  call Emit '1. The affected logstream directory is full. Determine why'
  call Emit '   old offload data sets remain referenced and whether the'
  call Emit '   stream retention/autodelete policy is appropriate.'
  call Emit '2. Review IXG301I and the exact logger return/reason code.'
  call Emit '3. Do not delete logger-managed data sets directly merely'
  call Emit '   to make catalog or DASD space appear available.'
end
else if delayEvidence then do
  call Emit '1. Start with the exact delayed request and any outstanding'
  call Emit '   IXG272E/IXG312E shown above.'
  call Emit '2. If the request is RECALLING, investigate the HSM recall.'
  call Emit '3. If the request is ALLOCATING, investigate SMS selection,'
  call Emit '   eligible volumes and real allocation failure messages.'
  call Emit '4. Review the SYSZLOGR resource display for serialization.'
  call Emit '   If a dependency exists, ENQDIAG can be used separately.'
  call Emit '5. Do not automatically reply FAIL to a logger delay WTOR.'
  call Emit '   Recovery remains a human/site-procedure decision.'
end
else if cfProblem then do
  call Emit '1. Review the affected logstream status and captured'
  call Emit '   D XCF,STR output before taking recovery action.'
  call Emit '2. STRUCTURE FULL, rebuild, loss-of-connectivity and'
  call Emit '   loss-of-data conditions are not equivalent problems.'
  call Emit '3. Preserve evidence before any disruptive CF/logger action.'
end
else do
  call Emit '1. No active failure was identified by the evidence this run.'
  call Emit '2. If the ticket refers to an earlier event, use archived'
  call Emit '   SYSLOG/OPERLOG evidence because current displays may be'
  call Emit '   healthy after the condition has cleared.'
  call Emit '3. Re-run against a named logstream when the affected stream'
  call Emit '   is known: TSO LOGRDIAG LOGSTREAM name'
end
return


Notes:
call Section 'IMPORTANT NOTES'
call Emit '  * LOGRDIAG is read-only.'
call Emit '  * It does not submit JCL or issue SETLOGR/SETXCF/REPLY.'
call Emit '  * Current-day SYSLOG history is supporting evidence; a'
call Emit '    message seen earlier today is not automatically still active.'
call Emit '  * D LOGGER runtime status is weighted more heavily for'
call Emit '    present-state conclusions.'
call Emit '  * IXG262A and DS DIRECTORY FULL are intentionally separate.'
call Emit '  * A logger allocation delay does not by itself prove a DASD'
call Emit '    capacity or SMS/storage-group failure.'
call Emit '  * LOGRDIAG HIGH/MEDIUM/LOW is a triage classification, not'
call Emit '    an IBM-defined message severity.'
call Emit '  * Results depend on SDSF, SAF and OPERCMDS visibility.'
call Emit '  * Under ISPF the persistent report opens in Browse.'
call Emit '  * SAMPLE mode is synthetic and issues no SDSF/console command.'
call Emit '  * Commands in COMMAND REFERENCE are informational; recovery'
call Emit '    commands are never executed by LOGRDIAG.'
return


Footer:
call Emit ''
call Emit copies('=',78)
if mode = 'SAMPLE' then
  call Emit ' LOGRDIAG SAMPLE completed. No live logger data was read.'
else
  call Emit ' LOGRDIAG completed. No logger/system changes were made.'
call Emit copies('=',78)
return


/*====================================================================*/
/* HELP / README                                                      */
/*====================================================================*/

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

call Emit 'NAME'
call Emit '  LOGRDIAG - z/OS System Logger health diagnostic utility.'
call Emit ''

call Emit 'PURPOSE'
call Emit '  LOGRDIAG is a read-only first-response utility for System'
call Emit '  Logger incidents such as IXG262A directory extent shortages,'
call Emit '  IXG271I delayed logger data set requests, failed/stalled'
call Emit '  offloads and abnormal CF/logstream states.'
call Emit ''

call Emit 'SYNTAX'
call Emit '  TSO LOGRDIAG'
call Emit '  TSO LOGRDIAG ALL'
call Emit '  TSO LOGRDIAG LOGSTREAM logstream.name'
call Emit '  TSO LOGRDIAG LSN logstream.name'
call Emit '  TSO LOGRDIAG logstream.name'
call Emit '  TSO LOGRDIAG SAMPLE'
call Emit '  TSO LOGRDIAG HELP'
call Emit ''

call Emit 'SAMPLE MODE'
call Emit '  TSO LOGRDIAG SAMPLE creates a synthetic demonstration report.'
call Emit '  It uses the real executing system name/date/time but does not'
call Emit '  start SDSF, read SYSLOG or issue any console command.'
call Emit '  The sample is saved separately in userid.LOGRDIAG.SAMPLE.'
call Emit ''
call Emit 'WHAT LOGRDIAG CHECKS'
call Emit '  1. D LOGGER,STATUS - whether System Logger is active.'
call Emit '  2. D LOGGER,L - logstream type, structure and runtime status.'
call Emit '  3. D LOGGER,STATUS,RECALLS - recall-related evidence.'
call Emit '  4. D XCF,COUPLE,TYPE=LOGR - active LOGR CDS information and'
call Emit '     formatted DSEXTENT capacity when shown by the system.'
call Emit '  5. D LOGGER,C,LSN=name,DETAIL for one focus logstream.'
call Emit '  6. D XCF,STR,STRNAME=name for a CF-backed focus stream.'
call Emit '  7. Current-day SYSLOG for selected IXG shortage/offload'
call Emit '     messages when the caller is authorized.'
call Emit '  8. SDSF SR for active IXG272E / IXG312E logger WTORs.'
call Emit '  9. D GRS,RES=(SYSZLOGR,*) only when delay evidence exists.'
call Emit ''

call Emit 'WHAT IT DISTINGUISHES'
call Emit '  * Global LOGR DSEXTENT shortage.'
call Emit '  * A specific logstream data set directory full condition.'
call Emit '  * Offload/data set allocation or recall delay.'
call Emit '    IXG271I ALLOCATION and MIGRATED DATASET forms are parsed.'
call Emit '  * CF structure/logstream availability conditions.'
call Emit '  * DASD/SMS causes that remain unproven from logger evidence.'
call Emit ''

call Emit 'IMPORTANT'
call Emit '  LOGRDIAG will not invent an exact directory used/total count.'
call Emit '  IXG261E/IXG262A provide TOTAL/IN USE/AVAILABLE counts'
call Emit '  when the message text is visible to this invocation.'
call Emit '  D XCF,COUPLE can also show formatted DSEXTENT capacity.'
call Emit '  Detailed per-logstream directory usage may require IXCMIAPU'
call Emit '  LIST LOGSTREAM NAME(*) DETAIL(YES) under site procedures.'
call Emit ''

call Emit 'OUTPUT DATA SETS'
call Emit "  '"reportDsn"'"
call Emit '     Persistent latest live diagnostic report.'
call Emit "  '"sampleDsn"'"
call Emit '     Persistent synthetic sample report.'
call Emit "  '"helpDsn"'"
call Emit '     Persistent HELP / README text.'
call Emit ''
call Emit '  SAMPLE and HELP do not overwrite the last live RPT.'
call Emit ''

call Emit 'SAFETY'
call Emit '  LOGRDIAG issues display/read requests only.'
call Emit '  It never issues SETLOGR, SETXCF, REPLY, FORCE, DELETE,'
call Emit '  DEFINE, UPDATE, CDS switch, logstream deletion or allocation.'
call Emit '  It does not submit JCL.'
call Emit ''

call Emit 'RESOURCE IMPACT'
call Emit '  The utility performs a small number of display commands plus'
call Emit '  bounded SDSF SYSLOG searches. It does not sample in a loop or'
call Emit '  hold resources. On a very large logger inventory, D LOGGER,L'
call Emit '  can return a large display, so the saved raw report is capped.'
return


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

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


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


Emit:
procedure expose rpt. rptCount
emOut = ''

do emI = 1 to arg()
  emPiece = arg(emI)
  if emI = 1 then emOut = emPiece
  else emOut = emOut emPiece
end

if length(emOut) <= 132 then do
  rptCount = rptCount + 1
  rpt.rptCount = emOut
end
else do
  emRest = emOut
  do while length(emRest) > 132
    rptCount = rptCount + 1
    rpt.rptCount = left(emRest,132)
    emRest = substr(emRest,133)
  end
  if emRest <> '' then do
    rptCount = rptCount + 1
    rpt.rptCount = emRest
  end
end

rpt.0 = rptCount
return


ValueOr:
procedure
parse arg voV, voD
voV = strip(voV)
if voV = '' then return voD
return voV


DisplayDate:
procedure
ddS = date('S')
ddY = left(ddS,4)
ddM = substr(ddS,5,2) + 0
ddD = substr(ddS,7,2)
ddMonths = 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'
return ddD'-'word(ddMonths,ddM)'-'ddY


StatusFromLine:
procedure
parse arg sfLine
sfU = translate(sfLine)

sfStatuses = 'DUPLEX TO SIMPLEX|DUPLEXING REBUILD|'
sfStatuses = sfStatuses'REBUILD IN PROGRESS|REBUILD FAILED|'
sfStatuses = sfStatuses'CONNECT PENDING|DISCONNECT PENDING|'
sfStatuses = sfStatuses'OFFLOAD IN PROGRESS|STRUCTURE FULL|'
sfStatuses = sfStatuses'LOSS OF DATA|LOST CONNECTIVITY|'
sfStatuses = sfStatuses'DS DIRECTORY FULL|DELETE IN PROGRESS|'
sfStatuses = sfStatuses'IN USE|AVAILABLE'

do sfI = 1 to CountParts(sfStatuses,'|')
  sfS = Part(sfStatuses,sfI,'|')
  if pos(sfS,sfU) > 0 then return sfS
end
return ''


ProblemStatus:
procedure
parse upper arg pbSt
if pbSt = 'STRUCTURE FULL' then return 1
if pbSt = 'LOSS OF DATA' then return 1
if pbSt = 'REBUILD IN PROGRESS' then return 1
if pbSt = 'DUPLEXING REBUILD' then return 1
if pbSt = 'DUPLEX TO SIMPLEX' then return 1
if pbSt = 'REBUILD FAILED' then return 1
if pbSt = 'LOST CONNECTIVITY' then return 1
if pbSt = 'DS DIRECTORY FULL' then return 1
if pbSt = 'CONNECT PENDING' then return 1
if pbSt = 'DISCONNECT PENDING' then return 1
return 0


StatusScore:
procedure
parse upper arg ssSt
select
  when ssSt = 'LOSS OF DATA' then return 130
  when ssSt = 'DS DIRECTORY FULL' then return 125
  when ssSt = 'REBUILD FAILED' then return 120
  when ssSt = 'LOST CONNECTIVITY' then return 115
  when ssSt = 'STRUCTURE FULL' then return 110
  when ssSt = 'REBUILD IN PROGRESS' then return 95
  when ssSt = 'DUPLEXING REBUILD' then return 90
  when ssSt = 'DUPLEX TO SIMPLEX' then return 90
  when ssSt = 'CONNECT PENDING' then return 75
  when ssSt = 'DISCONNECT PENDING' then return 75
  otherwise return 50
end


AddSuspect:
procedure expose suspectCount suspectName. suspectScore. ,
                 suspectReason. suspectIndex.
parse upper arg asName
parse arg , asScore, asReason

asName = strip(asName)
if asName = '' then return

asKey = c2x(asName)
asIdx = suspectIndex.asKey

if asIdx = 0 then do
  suspectCount = suspectCount + 1
  asIdx = suspectCount
  suspectIndex.asKey = asIdx
  suspectName.asIdx = asName
  suspectScore.asIdx = asScore
  suspectReason.asIdx = asReason
end
else if asScore > suspectScore.asIdx then do
  suspectScore.asIdx = asScore
  suspectReason.asIdx = asReason
end
return


ParseDsextentMessage:
parse arg dmLine
dmU = translate(dmLine)

dmT = pos('TOTAL:',dmU)
dmTLen = 6
if dmT = 0 then do
  dmT = pos('TOTAL ',dmU)
  dmTLen = 6
end

dmI = pos('IN-USE:',dmU)
dmILen = 7
if dmI = 0 then do
  dmI = pos('IN USE:',dmU)
  dmILen = 7
end

dmA = pos('AVAILABLE:',dmU)

if dmT > 0 & dmI > dmT then do
  dmX = strip(substr(dmLine,dmT+dmTLen,dmI-(dmT+dmTLen)))
  dmN = CleanToken(word(dmX,1))
  if datatype(dmN,'W') then dsextentTotal = dmN
end

if dmI > 0 & dmA > dmI then do
  dmX = strip(substr(dmLine,dmI+dmILen,dmA-(dmI+dmILen)))
  dmN = CleanToken(word(dmX,1))
  if datatype(dmN,'W') then dsextentInUse = dmN
end

if dmA > 0 then do
  dmX = strip(substr(dmLine,dmA+10))
  dmN = CleanToken(word(dmX,1))
  if datatype(dmN,'W') then dsextentAvail = dmN
end
return

ExtractLogstream:
procedure
parse arg elLine
elU = translate(elLine)

elMarker = 'FOR LOGSTREAM '
elP = pos(elMarker,elU)

if elP = 0 then do
  elMarker = 'FOR LOG STREAM '
  elP = pos(elMarker,elU)
end

if elP = 0 then do
  elMarker = 'LOGSTREAM '
  elP = pos(elMarker,elU)
end

if elP = 0 then do
  elMarker = 'LOG STREAM '
  elP = pos(elMarker,elU)
end

if elP = 0 then return ''

elX = strip(substr(elLine,elP+length(elMarker)))
elName = translate(CleanToken(word(elX,1)))

if elName = '' then return ''
if elName = 'IS' | elName = 'DATA' | elName = 'IN' then return ''
if elName = 'REQUEST' | elName = 'DIRECTORY' then return ''
if pos('=',elName) > 0 then return ''
if length(elName) > 26 then return ''
return elName


ExtractDsn:
procedure
parse arg edLine
edU = translate(edLine)
edP = pos('DSN=',edU)

if edP = 0 then return ''

edX = strip(substr(edLine,edP+4))
edDsn = CleanToken(word(edX,1))

if translate(edDsn) = 'LOGR' then return 'LOGR'
if translate(edDsn) = 'CDS' then return 'CDS'
return edDsn


CleanToken:
procedure
parse arg ct
ct = strip(ct)

do while ct <> ''
  ctR = right(ct,1)
  if ctR = ',' | ctR = '.' | ctR = ')' | ctR = ';' then
    ct = left(ct,length(ct)-1)
  else leave
end

do while ct <> ''
  ctL = left(ct,1)
  if ctL = '(' then ct = substr(ct,2)
  else leave
end

return strip(ct)


CountParts:
procedure
parse arg cpText, cpDelim
if cpText = '' then return 0
cpN = 1
cpP = pos(cpDelim,cpText)

do while cpP > 0
  cpN = cpN + 1
  cpText = substr(cpText,cpP+length(cpDelim))
  cpP = pos(cpDelim,cpText)
end
return cpN


Part:
procedure
parse arg ptText, ptWant, ptDelim
ptI = 1

do forever
  ptP = pos(ptDelim,ptText)

  if ptI = ptWant then do
    if ptP = 0 then return ptText
    return left(ptText,ptP-1)
  end

  if ptP = 0 then return ''
  ptText = substr(ptText,ptP+length(ptDelim))
  ptI = ptI + 1
end


GetVar:
procedure
parse upper arg gvName
parse arg , gvRow, gvDefault

gvVar = gvName'.'gvRow
if symbol(gvVar) = 'VAR' then return value(gvVar)
return gvDefault


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

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


/*====================================================================*/
/* REPORT DATA SET                                                    */
/*====================================================================*/

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 mode = 'HELP' then prDsn = helpDsn
else if mode = 'SAMPLE' then prDsn = sampleDsn
else prDsn = reportDsn

prDdn = 'LGRPT001'

/* Replace prior latest copy, but keep the newly written data set. */
prQ = outtrap('lrQuiet.')
Address TSO "DELETE '"prDsn"'"
prQ = outtrap('OFF')

prAlloc = "ALLOC DA('"prDsn"') F("prDdn") NEW CATALOG"
prAlloc = prAlloc' TRACKS SPACE(10 5) DSORG(PS)'
prAlloc = prAlloc' RECFM(F B) LRECL(133) BLKSIZE(0)'
Address TSO prAlloc
prRc = rc

if prRc <> 0 then do
  say 'LOGRDIAG: Unable to allocate output data set:'
  say "  '"prDsn"'"
  do prI = 1 to rptCount
    say rpt.prI
  end
  return
end

Address TSO 'EXECIO 'rptCount' DISKW 'prDdn' (STEM rpt. FINIS)'
prWrc = rc
Address TSO 'FREE F('prDdn')'

if prWrc <> 0 then do
  say 'LOGRDIAG: Unable to write output data set:'
  say "  '"prDsn"'"
  do prI = 1 to rptCount
    say rpt.prI
  end
  return
end

if ispfActive = 0 then do
  say "LOGRDIAG output saved in '"prDsn"'"
  do prI = 1 to rptCount
    say rpt.prI
  end
  return
end

Address ISPEXEC 'CONTROL ERRORS RETURN'
Address ISPEXEC "BROWSE DATASET('"prDsn"')"
prBrc = rc
Address ISPEXEC 'CONTROL ERRORS CANCEL'

/* Intentionally keep the report after Browse ends. */
if prBrc > 4 then do
  say 'LOGRDIAG: ISPF Browse failed RC='prBrc
  say "LOGRDIAG report remains saved in '"prDsn"'"
  do prI = 1 to rptCount
    say rpt.prI
  end
end
return


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


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