/* REXX */
/*--------------------------------------------------------------------*/
/* LONGDIAG - z/OS running-long job diagnostic                       */
/*                                                                    */
/* Version : 1.4                                                      */
/* Author  : SARATH S                                                 */
/* Purpose : Read-only first-response analysis for an active job or   */
/*           started task that appears to be running longer than      */
/*           expected.                                                */
/*                                                                    */
/* Examples:                                                          */
/*   TSO LONGDIAG PAYJOB                                              */
/*   TSO LONGDIAG PAYJOB JOBID JOB12345                               */
/*   TSO LONGDIAG PAYJOB SYS MVSD                                     */
/*   TSO LONGDIAG PAYJOB SYS MVSD ASID 02F1                           */
/*   TSO LONGDIAG PAYJOB SAMPLES 3 INTERVAL 5                         */
/*   TSO LONGDIAG HELP                                                */
/*   TSO LONGDIAG SAMPLE                                              */
/*                                                                    */
/* Output:                                                            */
/*   <userid>.LONGDIAG.Dyymmdd.Thhmmss                                */
/*   <userid>.LONGDIAG.SAMPLE.Dyymmdd.Thhmmss                         */
/*   <userid>.LONGDIAG.HELP.Dyymmdd.Thhmmss                           */
/*                                                                    */
/* Design:                                                            */
/* - Does not require SDSF JY or JT panels.                            */
/* - Uses ST DelayRsn/Dly when those columns are available.            */
/* - Uses repeated DA samples to check CPU/EXCP progress.              */
/* - Checks outstanding replies through SDSF SR.                       */
/* - Captures one targeted D GRS,ANALYZE,DEPENDENCY display.           */
/* - Adds current target-system CPU/storage context from SDSF SYS.     */
/* - Degrades when optional columns/commands are unavailable.          */
/* - Read-only. No CANCEL, FORCE, REPLY, DEQ or system change.         */
/*--------------------------------------------------------------------*/

numeric digits 20
signal on syntax name FatalSyntax

ldVersion = '1.4'

/*----------------------------- Defaults ----------------------------*/
mode = 'RUN'
targetJob = ''
targetJobid = ''
targetSys = ''
targetAsid = ''

sampleCount = 2
sampleInterval = 5
analysisCount = 10
maxGrsLines = 14
maxWtorLines = 5

rpt. = ''
rpt.0 = 0
rptCount = 0
ispfActive = 0
sdsfOn = 0
ussOn = 0

uid = sysvar('SYSUID')
localSys = translate(MVSVAR('SYSNAME'))
reportDsn = ''
helpDsn = ''
sampleDsn = ''

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

/*------------------------- Collection state ------------------------*/
stAvail = 0
stDelaySupported = 0
stMatchCount = 0
stRc = -1
stRows = 0
stCols = ''
stDisplay = ''
stMsg = ''
stJobid = ''
stOwner = ''
stQueue = ''
stStatus = ''
stPhase = ''
stSys = ''
stAsidx = ''
stSrvClass = ''
stMode = ''
stDly = ''
stDelayRsn = ''

daAvail = 0
daLevel = 0
daRc = -1
daRows = 0
daCols = ''
daDisplay = ''
daMsg = ''
candidateCount = 0
targetResolved = 0
targetOwner = ''
targetStatus = ''
targetStep = ''
targetProcStep = ''
targetStart = ''
targetElapsed = ''
targetSrvClass = ''
targetCpuLimit = ''

sampleOK. = 0
sampleTime. = ''
sampleCpu. = ''
sampleExcp. = ''
sampleCpuPct. = ''
sampleSio. = ''
samplePaging. = ''
sampleStep. = ''
sampleProc. = ''
validSamples = 0

cpuDelta = -1
excpDelta = -1
progressState = 'UNDETERMINED'
currentCpuPct = -1
currentSio = -1
currentPaging = -1

sysAvail = 0
sysCpu = -1
sysPage = -1
sysRealPct = -1
sysAuxPct = -1
sysSio = -1

srAvail = 0
wtorCount = 0
wtorLine. = ''

grsTried = 0
grsAvail = 0
grsRc = -1
grsWaiting = 0
grsDeadlock = 0
grsNoWait = 0
grsWaitTime = ''
grsQname = ''
grsRname = ''
grsBlocker = ''
grsBlockerSys = ''
grsHit. = ''
grsHitCount = 0

diagnosis = 'INDETERMINATE'
confidence = 'LOW'
severity = 'UNKNOWN'
delayCategory = ''
limitNote = ''

/*--------------------------- Arguments -----------------------------*/
parse arg rawArgs
call ParseArguments rawArgs
paRc = result

if paRc <> 0 then do
  say 'LONGDIAG: Invalid invocation.'
  say 'Use TSO LONGDIAG HELP'
  exit 12
end

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

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

/*--------------------------- SDSF setup ----------------------------*/
rc = isfcalls('ON')
if rc <> 0 then do
  call Emit 'LONGDIAG: Unable to establish the SDSF REXX environment.'
  call Emit 'RC='rc
  call Emit 'Use TSO LONGDIAG HELP for requirements and limitations.'
  call PresentReport
  exit 20
end
sdsfOn = 1

/* Start with all SDSF REXX input variables undefined. Defining an    */
/* SDSF special variable causes its associated command to be issued,  */
/* so set only the inputs required for this request.                  */
rrc = isfreset('INPUT')
isfprefix = targetJob || '*'
isfowner = '*'

/* Resolve JES identity first. ST exposes JOBID, SYSNAME, ASIDX and   */
/* DelayRsn for active JES2 work without requiring JY/JT.             */
call FetchSTTarget

if stMatchCount = 1 then do
  if targetJobid = '' then targetJobid = translate(stJobid)
  if targetSys = '' then targetSys = translate(stSys)
  if targetAsid = '' & stAsidx <> '' then targetAsid = stAsidx
  if targetOwner = '' then targetOwner = stOwner
  if targetStatus = '' then targetStatus = stStatus
  if targetSrvClass = '' then targetSrvClass = stSrvClass
end

/* Scope DA only when the target execution system is known. If ST did */
/* not resolve SYSNAME, leave ISFSYSNAME undefined so DA can return   */
/* every system visible to this SDSF session.                         */
if targetSys <> '' then isfsysname = targetSys
else drop isfsysname

call FetchDARows
firstDaRc = result

if firstDaRc = 0 then do
  call SelectTargetDA
  selRc = result

  if selRc = 4 then do
    call Header
    call Section 'TARGET RESOLUTION'
    call Emit 'More than one active address space matches the request.'
    call Emit 'LONGDIAG will not guess which instance is the ticket target.'
    call Emit ''
    call CandidateReport
    call Emit ''
    call Emit 'Retry with JOBID, SYS or ASID, for example:'
    call Emit '  TSO LONGDIAG 'targetJob' JOBID JOB12345'
    call Footer
    call Cleanup
    call PresentReport
    exit 8
  end

  if selRc = 0 then do
    targetResolved = 1
    call CaptureCurrentTarget 1
    if result = 0 then do
      sampleOK.1 = 1
      sampleTime.1 = time('N')
      validSamples = 1
    end
  end
end

/* Re-read ST after DA resolution so DelayRsn and identity reflect    */
/* the exact JOBID/system when duplicate jobnames exist.              */
if targetResolved then call FetchSTTarget

/* Timed sampling is optional. If USS sleep cannot be initialized,    */
/* keep the first DA snapshot rather than failing the utility.         */
if targetResolved & sampleCount > 1 then do
  ussRc = syscalls('ON')
  if ussRc = 0 then ussOn = 1
  else do
    ussOn = 0
    sampleCount = 1
    limitNote = 'Timed sampling unavailable; one DA snapshot used.'
  end
end

if targetResolved & sampleCount > 1 then do
  do s = 2 to sampleCount
    call SleepSeconds sampleInterval
    call FetchDARows
    fdRc = result

    if fdRc = 0 then do
      call CaptureCurrentTarget s
      ctRc = result
      if ctRc = 0 then do
        sampleOK.s = 1
        sampleTime.s = time('N')
        validSamples = validSamples + 1
      end
    end
  end
end

call EvaluateProgress
call FetchSystemContext
call FetchSREvidence
call CaptureGRSEvidence
call EvaluateDiagnosis

/*----------------------------- Report ------------------------------*/
call Header
call TriageSummary
call TargetReport
call CollectionReport
call DelayReport
call ProgressReport
call SystemReport
call OperatorReport
call GRSReport
call CauseReport
call Recommendations
call CommandReference
call Notes
call Footer
call Cleanup
call PresentReport
exit 0


/*====================================================================*/
/* ARGUMENT PARSING                                                   */
/*====================================================================*/

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

if paRaw = '' then do
  say 'LONGDIAG: A jobname is required.'
  return 12
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'
  return 0
end

if paFirst = 'JOB' then do
  targetJob = word(paRaw,2)
  paI = 3
end
else do
  targetJob = paFirst
  paI = 2
end

if targetJob = '' then do
  say 'LONGDIAG: A jobname is required.'
  return 12
end

if length(targetJob) > 8 then do
  say 'LONGDIAG: Jobname cannot exceed 8 characters.'
  return 12
end

paWords = words(paRaw)

do while paI <= paWords
  paKey = word(paRaw,paI)
  paVal = word(paRaw,paI+1)

  if paVal = '' then do
    say 'LONGDIAG: Missing value after' paKey
    return 12
  end

  select
    when paKey = 'JOBID' then do
      targetJobid = paVal
      paI = paI + 2
    end

    when paKey = 'SYS' | paKey = 'SYSTEM' then do
      targetSys = paVal
      paI = paI + 2
    end

    when paKey = 'ASID' then do
      targetAsid = StripHex(paVal)
      paI = paI + 2
    end

    when paKey = 'SAMPLES' then do
      if datatype(paVal,'W') = 0 then do
        say 'LONGDIAG: SAMPLES must be a whole number.'
        return 12
      end
      sampleCount = paVal
      if sampleCount < 1 then sampleCount = 1
      if sampleCount > 3 then sampleCount = 3
      paI = paI + 2
    end

    when paKey = 'INTERVAL' then do
      if datatype(paVal,'W') = 0 then do
        say 'LONGDIAG: INTERVAL must be a whole number.'
        return 12
      end
      sampleInterval = paVal
      if sampleInterval < 2 then sampleInterval = 2
      if sampleInterval > 30 then sampleInterval = 30
      paI = paI + 2
    end

    otherwise do
      say 'LONGDIAG: Unknown parameter:' paKey
      return 12
    end
  end
end

if length(targetSys) > 8 then do
  say 'LONGDIAG: System name cannot exceed 8 characters.'
  return 12
end

return 0


/*====================================================================*/
/* SDSF ST / DELAYRSN                                                 */
/*====================================================================*/

FetchSTTarget:
stAvail = 0
stDelaySupported = 0
stMatchCount = 0
stRc = -1
stRows = 0
stCols = ''
stDisplay = ''
stMsg = ''

drop JNAME. JOBID. OWNERID. QUEUE. STATUS. PHASENAME. PHASE.
drop SYSNAME. ASID. ASIDX. SRVCLS. DELAY. DELAYRSN. SSMODE.
drop ISFROWS ISFDISPLAY ISFMSG ISFMSG2.

isfcols = 'JNAME JOBID OWNERID QUEUE STATUS PHASENAME PHASE '
isfcols = isfcols'SYSNAME ASID ASIDX SRVCLS DELAY DELAYRSN SSMODE'

Address SDSF 'ISFEXEC ST'
fstRc = rc
stRc = fstRc

if symbol('ISFROWS') = 'VAR' then stRows = isfrows
if symbol('ISFCOLS') = 'VAR' then stCols = strip(isfcols)
if symbol('ISFDISPLAY') = 'VAR' then stDisplay = strip(isfdisplay)
call SaveSdsfMessage
stMsg = result

if fstRc <> 0 then return fstRc

stAvail = 1
if wordpos('DELAYRSN',translate(stCols)) > 0 then
  stDelaySupported = 1

do fstI = 1 to stRows
  fstJob = translate(GetVar('JNAME',fstI,''))
  if fstJob <> targetJob then iterate

  fstId = translate(GetVar('JOBID',fstI,''))
  fstSys = translate(GetVar('SYSNAME',fstI,''))

  if targetJobid <> '' & fstId <> '' then do
    if fstId <> targetJobid then iterate
  end

  if targetSys <> '' & fstSys <> '' then do
    if fstSys <> targetSys then iterate
  end

  stMatchCount = stMatchCount + 1

  if stMatchCount = 1 then do
    stJobid = GetVar('JOBID',fstI,'')
    stOwner = GetVar('OWNERID',fstI,'')
    stQueue = GetVar('QUEUE',fstI,'')
    stStatus = GetVar('STATUS',fstI,'')
    stPhase = GetVar('PHASENAME',fstI,'')
    stSys = GetVar('SYSNAME',fstI,'')
    stAsidx = StripHex(GetVar('ASIDX',fstI,''))
    stSrvClass = GetVar('SRVCLS',fstI,'')
    stMode = GetVar('SSMODE',fstI,'')
    stDly = GetVar('DELAY',fstI,'')
    stDelayRsn = GetVar('DELAYRSN',fstI,'')
  end
end

return 0

/*====================================================================*/
/* SDSF DA / TARGET RESOLUTION                                        */
/*====================================================================*/

FetchDARows:
daAvail = 0
daLevel = 0
daRc = -1
daRows = 0
daCols = ''
daDisplay = ''
daMsg = ''

drop JNAME. JOBID. OWNERID. ASID. ASIDX. SYSNAME. CPU. EXCP.
drop CPUPR. EXCPRT. PAGING. SRVCLASS. STATUS. STEPN. PROCS.
drop REAL. SCPU. SPAGING.
drop ISFROWS ISFDISPLAY ISFMSG ISFMSG2.

isfcols = 'JNAME JOBID OWNERID ASID ASIDX SYSNAME CPU EXCP CPUPR '
isfcols = isfcols'EXCPRT PAGING SRVCLASS STATUS STEPN PROCS REAL '
isfcols = isfcols'SCPU SPAGING'

Address SDSF 'ISFEXEC DA'
fdrRc = rc
daRc = fdrRc

if symbol('ISFROWS') = 'VAR' then daRows = isfrows
if symbol('ISFCOLS') = 'VAR' then daCols = strip(isfcols)
if symbol('ISFDISPLAY') = 'VAR' then daDisplay = strip(isfdisplay)
call SaveSdsfMessage
daMsg = result

if fdrRc <> 0 then return fdrRc

daAvail = 1

if wordpos('SYSNAME',translate(daCols)) > 0 then daLevel = 3
else if wordpos('CPUPR',translate(daCols)) > 0 then daLevel = 2
else daLevel = 1

return 0

SelectTargetDA:
candidateCount = 0
candidateRow. = 0
candidateJobid. = ''
candidateSys. = ''
candidateAsid. = ''
candidateStatus. = ''
candidateElapsed. = ''

do stdI = 1 to daRows
  stdJob = translate(GetVar('JNAME',stdI,''))
  if stdJob <> targetJob then iterate

  stdId = translate(GetVar('JOBID',stdI,''))
  stdSys = translate(GetVar('SYSNAME',stdI,''))
  stdAx = StripHex(GetVar('ASIDX',stdI,''))
  stdAd = StripHex(GetVar('ASID',stdI,''))

  if targetJobid <> '' & stdId <> '' then do
    if stdId <> targetJobid then iterate
  end

  if targetSys <> '' & stdSys <> '' then do
    if stdSys <> targetSys then iterate
  end

  if targetAsid <> '' then do
    if stdAx <> '' | stdAd <> '' then do
      if stdAx <> targetAsid & stdAd <> targetAsid then iterate
    end
  end

  candidateCount = candidateCount + 1
  c = candidateCount
  candidateRow.c = stdI
  candidateJobid.c = GetVar('JOBID',stdI,'-')
  candidateSys.c = GetVar('SYSNAME',stdI,'-')

  if stdAx <> '' then candidateAsid.c = stdAx
  else if stdAd <> '' then candidateAsid.c = stdAd
  else candidateAsid.c = '-'

  candidateStatus.c = GetVar('STATUS',stdI,'-')
  candidateElapsed.c = '-'
end

if candidateCount = 0 then return 8
if candidateCount > 1 then return 4

targetRow = candidateRow.1

if targetJobid = '' then
  targetJobid = translate(GetVar('JOBID',targetRow,''))

if targetSys = '' then
  targetSys = translate(GetVar('SYSNAME',targetRow,''))

if targetAsid = '' then do
  trAx = StripHex(GetVar('ASIDX',targetRow,''))
  trAd = StripHex(GetVar('ASID',targetRow,''))
  if trAx <> '' then targetAsid = trAx
  else targetAsid = trAd
end

targetStatus = GetVar('STATUS',targetRow,targetStatus)
targetOwner = GetVar('OWNERID',targetRow,targetOwner)
targetSrvClass = GetVar('SRVCLASS',targetRow,targetSrvClass)
targetStep = GetVar('STEPN',targetRow,targetStep)
targetProcStep = GetVar('PROCS',targetRow,targetProcStep)

return 0


CaptureCurrentTarget:
parse arg cctSample

best = 0

do cctI = 1 to daRows
  cctJob = translate(GetVar('JNAME',cctI,''))
  if cctJob <> targetJob then iterate

  cctId = translate(GetVar('JOBID',cctI,''))
  cctSys = translate(GetVar('SYSNAME',cctI,''))
  cctAx = StripHex(GetVar('ASIDX',cctI,''))
  cctAd = StripHex(GetVar('ASID',cctI,''))

  if targetJobid <> '' & cctId <> '' then do
    if cctId <> targetJobid then iterate
  end

  if targetSys <> '' & cctSys <> '' then do
    if cctSys <> targetSys then iterate
  end

  if targetAsid <> '' then do
    if cctAx <> '' | cctAd <> '' then do
      if cctAx <> targetAsid & cctAd <> targetAsid then iterate
    end
  end

  best = cctI
  leave
end

if best = 0 then return 8

sampleCpu.cctSample = GetVar('CPU',best,'')
sampleExcp.cctSample = GetVar('EXCP',best,'')
sampleCpuPct.cctSample = GetVar('CPUPR',best,'')
sampleSio.cctSample = GetVar('EXCPRT',best,'')
samplePaging.cctSample = GetVar('PAGING',best,'')
sampleStep.cctSample = GetVar('STEPN',best,targetStep)
sampleProc.cctSample = GetVar('PROCS',best,targetProcStep)

targetStatus = GetVar('STATUS',best,targetStatus)
targetOwner = GetVar('OWNERID',best,targetOwner)
targetSrvClass = GetVar('SRVCLASS',best,targetSrvClass)

if sampleStep.cctSample <> '' then targetStep = sampleStep.cctSample
if sampleProc.cctSample <> '' then targetProcStep = sampleProc.cctSample

return 0


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

EvaluateProgress:
first = 0
last = 0

do epI = 1 to sampleCount
  if sampleOK.epI then do
    if first = 0 then first = epI
    last = epI
  end
end

if last > 0 then do
  currentCpuPct = MetricNumber(sampleCpuPct.last)
  currentSio = MetricNumber(sampleSio.last)
  currentPaging = MetricNumber(samplePaging.last)
end

if validSamples < 2 then do
  if validSamples = 1 then progressState = 'SINGLE SAMPLE - UNDETERMINED'
  else progressState = 'DA PROGRESS DATA UNAVAILABLE'
  return
end

fStep = strip(sampleStep.first)
lStep = strip(sampleStep.last)
fProc = strip(sampleProc.first)
lProc = strip(sampleProc.last)

if fStep <> '' & lStep <> '' then do
  if fStep <> lStep | fProc <> lProc then do
    progressState = 'PROGRESS OBSERVED - STEP/PROCSTEP CHANGED'
    return
  end
end

c1 = MetricNumber(sampleCpu.first)
c2 = MetricNumber(sampleCpu.last)
x1 = MetricNumber(sampleExcp.first)
x2 = MetricNumber(sampleExcp.last)

if c1 >= 0 & c2 >= 0 then cpuDelta = c2 - c1
if x1 >= 0 & x2 >= 0 then excpDelta = x2 - x1

if cpuDelta > 0.01 | excpDelta > 0 then
  progressState = 'ACTIVE / PROGRESS OBSERVED'
else if cpuDelta >= 0 & excpDelta >= 0 then
  progressState = 'NO CPU OR EXCP PROGRESS OBSERVED'
else
  progressState = 'PARTIAL DATA - PROGRESS UNDETERMINED'

return


/*====================================================================*/
/* SDSF SYS CONTEXT                                                   */
/*====================================================================*/

FetchSystemContext:
sysAvail = 0

drop SYSNAME. CPUPR. PAGERATE. REALPCT. AUXPCT. SIO.
isfcols = 'SYSNAME CPUPR PAGERATE REALPCT AUXPCT SIO'
Address SDSF 'ISFEXEC SYS'
fscRc = rc

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

if fscRc <> 0 then return fscRc
if isfrows = 0 then return 8

pick = 0

do fscI = 1 to isfrows
  fscSys = translate(GetVar('SYSNAME',fscI,''))
  if targetSys <> '' then do
    if fscSys = targetSys then do
      pick = fscI
      leave
    end
  end
  else if fscSys = localSys then do
    pick = fscI
    leave
  end
end

if pick = 0 then do
  if targetSys = '' then pick = 1
  else return 8
end

sysAvail = 1
sysCpu = MetricNumber(GetVar('CPUPR',pick,''))
sysPage = MetricNumber(GetVar('PAGERATE',pick,''))
sysRealPct = MetricNumber(GetVar('REALPCT',pick,''))
sysAuxPct = MetricNumber(GetVar('AUXPCT',pick,''))
sysSio = MetricNumber(GetVar('SIO',pick,''))

return 0


/*====================================================================*/
/* SDSF SR / OUTSTANDING REQUESTS                                     */
/*====================================================================*/

FetchSREvidence:
srAvail = 0
wtorCount = 0
wtorLine. = ''

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'
fsrRc = rc

if fsrRc <> 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'
  fsrRc = rc
end

if fsrRc <> 0 then return fsrRc

srAvail = 1

do fsrI = 1 to isfrows
  fsrJob = translate(GetVar('JNAME',fsrI,''))
  fsrSys = translate(GetVar('SYSNAME',fsrI,''))
  fsrId = translate(GetVar('JOBID',fsrI,''))

  if fsrJob <> targetJob then iterate

  if targetSys <> '' & fsrSys <> '' then do
    if fsrSys <> targetSys then iterate
  end

  if targetJobid <> '' & fsrId <> '' then do
    if fsrId <> targetJobid then iterate
  end

  wtorCount = wtorCount + 1

  if wtorCount <= maxWtorLines then do
    rep = GetVar('REPLYID',fsrI,'-')
    age = GetVar('ELAPSED',fsrI,'')
    txt = GetVar('MSGTEXT',fsrI,'')
    ln = 'Reply='rep
    if age <> '' then ln = ln' Age='age
    ln = ln' 'txt
    wtorLine.wtorCount = ShortText(ln,120)
  end
end

return 0


/*====================================================================*/
/* TARGETED GRS ANALYSIS                                              */
/*====================================================================*/

CaptureGRSEvidence:
grsTried = 0
grsAvail = 0
grsWaiting = 0
grsNoWait = 0
grsDeadlock = 0
grsHitCount = 0
grsHit. = ''

if targetSys = '' then return 4

grsCmd = 'D GRS,ANALYZE,DEPENDENCY,SYSTEM='targetSys','

if targetAsid <> '' then
  grsCmd = grsCmd'ASID='targetAsid',COUNT='analysisCount',DETAIL'
else
  grsCmd = grsCmd'JOBNAME='targetJob',COUNT='analysisCount',DETAIL'

grsTried = 1
drop ISFULOG.
isfdelay = '5'
Address SDSF "ISFSLASH '"grsCmd"'"
grsRc = rc
drop isfdelay

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

grsAvail = 1
expectResource = 0
expectBlocker = 0
seenSource = 0
pendingSys = ''

do cgrI = 1 to isfulog.0
  ln = strip(isfulog.cgrI)
  up = translate(ln)

  if pos('DEADLOCK',up) > 0 & pos('NO DEADLOCK',up) = 0 then
    grsDeadlock = 1

  if pos('ANALYSIS ENDED:',up) > 0 then do
    if pos('NOT WAITING',up) > 0 then grsNoWait = 1
  end

  if pos('SYSTEM:',up) > 0 then do
    p = pos('SYSTEM:',up)
    pendingSys = strip(word(substr(ln,p+7),1))
  end

  if pos('JOBNAME:',up) > 0 then do
    p = pos('JOBNAME:',up)
    rest = strip(substr(ln,p+8))
    jb = translate(word(rest,1))

    if seenSource = 0 then do
      if jb = targetJob then seenSource = 1
    end
    else if expectBlocker then do
      if jb <> '' & jb <> targetJob then do
        if grsBlocker = '' then grsBlocker = jb
        if grsBlockerSys = '' then grsBlockerSys = pendingSys
      end
      expectBlocker = 0
    end
  end

  if pos('WAITING ',up) > 0 & pos(' FOR ',up) > 0 then do
    grsWaiting = 1
    p = pos('WAITING ',up)
    rest = strip(substr(ln,p+8))
    grsWaitTime = word(rest,1)
    expectResource = 1
  end
  else if expectResource then do
    if ln <> '' then do
      if pos('SYSTEM:',up) = 0 & pos('JOBNAME:',up) = 0 & ,
         pos('REQUEST:',up) = 0 & pos('ANALYSIS',up) = 0 then do
        grsQname = word(ln,1)
        grsRname = strip(substr(ln,length(grsQname)+1))
        expectResource = 0
        expectBlocker = 1
      end
    end
  end

  keep = 0
  if pos('GRS ANALYSIS',up) > 0 then keep = 1
  if pos('DEPENDENCY ANALYSIS:',up) > 0 then keep = 1
  if pos('SYSTEM:',up) > 0 then keep = 1
  if pos('JOBNAME:',up) > 0 then keep = 1
  if pos('WAITING ',up) > 0 then keep = 1
  if pos('ANALYSIS ENDED:',up) > 0 then keep = 1
  if pos('DEADLOCK',up) > 0 then keep = 1

  if keep & grsHitCount < maxGrsLines then do
    grsHitCount = grsHitCount + 1
    grsHit.grsHitCount = ShortText(ln,120)
  end
end

return 0


/*====================================================================*/
/* DIAGNOSIS                                                          */
/*====================================================================*/

EvaluateDiagnosis:
severity = 'LOW'
confidence = 'LOW'
diagnosis = 'INDETERMINATE'
delayCategory = ClassifyDelay(stDelayRsn)

if wtorCount > 0 then do
  diagnosis = 'WAITING FOR OPERATOR RESPONSE / WTOR'
  confidence = 'HIGH'
  severity = 'HIGH'
  return
end

if grsDeadlock then do
  diagnosis = 'GRS ANALYSIS REPORTED DEADLOCK TEXT'
  confidence = 'HIGH'
  severity = 'HIGH'
  return
end

if grsWaiting then do
  diagnosis = 'GRS / RESOURCE CONTENTION'
  if grsQname <> '' | grsBlocker <> '' then confidence = 'HIGH'
  else confidence = 'MEDIUM'
  severity = 'HIGH'
  return
end

if strip(stDelayRsn) <> '' then do
  diagnosis = delayCategory
  confidence = 'MEDIUM'
  severity = 'MEDIUM'
  return
end

if progressState = 'ACTIVE / PROGRESS OBSERVED' | ,
   left(progressState,17) = 'PROGRESS OBSERVED' then do

  if currentCpuPct >= 50 then
    diagnosis = 'CPU-INTENSIVE PROCESSING - JOB IS PROGRESSING'
  else if excpDelta > 0 | currentSio > 0 then
    diagnosis = 'I/O OR WORKLOAD PROCESSING - JOB IS PROGRESSING'
  else
    diagnosis = 'JOB IS MAKING PROGRESS'

  confidence = 'MEDIUM'
  severity = 'LOW'
  return
end

if progressState = 'NO CPU OR EXCP PROGRESS OBSERVED' then do
  if sysCpu >= 90 & currentCpuPct >= 0 & currentCpuPct < 5 then do
    diagnosis = 'POSSIBLE CPU STARVATION / SYSTEM CPU PRESSURE'
    confidence = 'MEDIUM'
    severity = 'MEDIUM'
    return
  end

  if sysRealPct >= 95 & sysPage > 0 then do
    diagnosis = 'POSSIBLE STORAGE / PAGING PRESSURE'
    confidence = 'MEDIUM'
    severity = 'MEDIUM'
    return
  end

  diagnosis = 'NO PROGRESS OBSERVED - SYSTEM CAUSE NOT IDENTIFIED'
  confidence = 'LOW'
  severity = 'MEDIUM'
  return
end

if targetResolved = 0 then do
  diagnosis = 'TARGET RUNTIME DATA UNAVAILABLE'
  confidence = 'LOW'
  severity = 'UNKNOWN'
  return
end

diagnosis = 'SYSTEM-LEVEL CAUSE NOT IDENTIFIED'
confidence = 'LOW'
severity = 'UNKNOWN'
return


ClassifyDelay:
procedure
parse upper arg cd
cd = strip(cd)

if cd = '' then return 'NO JES2 DELAY REASON REPORTED'

if pos('WTOR',cd) > 0 | pos('REPLY',cd) > 0 | ,
   pos('OPERATOR',cd) > 0 then
  return 'JES2 REPORTED OPERATOR / REPLY DELAY'

if pos('ENQ',cd) > 0 | pos('RESOURCE',cd) > 0 | ,
   pos('DATASET',cd) > 0 | pos('DATA SET',cd) > 0 then
  return 'JES2 REPORTED RESOURCE / DATA SET DELAY'

if pos('MOUNT',cd) > 0 | pos('TAPE',cd) > 0 | ,
   pos('DEVICE',cd) > 0 then
  return 'JES2 REPORTED DEVICE / MOUNT DELAY'

if pos('HSM',cd) > 0 | pos('RECALL',cd) > 0 | ,
   pos('MIGRAT',cd) > 0 then
  return 'JES2 REPORTED HSM / DATA SET AVAILABILITY DELAY'

if pos('SPOOL',cd) > 0 | pos('JES',cd) > 0 then
  return 'JES2 REPORTED JES / SPOOL DELAY'

if pos('XCF',cd) > 0 then
  return 'JES2 REPORTED XCF-RELATED DELAY'

return 'JES2 REPORTED DELAY: 'cd


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

Header:
call Emit ''
call Emit copies('=',78)
call Emit ' LONGDIAG - RUNNING-LONG JOB DIAGNOSTIC UTILITY'
call KV 'Version', ldVersion
call KV 'Author', 'SARATH S'
call KV 'Report mode', 'LIVE'
call KV 'Date', DisplayDate()
call KV 'Time', time('N')
call KV 'System', localSys
call Emit copies('=',78)
return


TriageSummary:
call Section 'TRIAGE SUMMARY'
call KV 'Severity', severity
call KV 'Primary diagnosis', diagnosis
call KV 'Confidence', confidence
call KV 'Job progress', progressState

if strip(stDelayRsn) <> '' then
  call KV 'ST DelayRsn', ShortText(stDelayRsn,88)
else if stDelaySupported then
  call KV 'ST DelayRsn', '<blank>'
else
  call KV 'ST DelayRsn', 'UNAVAILABLE'

if wtorCount > 0 then call KV 'Outstanding replies', wtorCount
else if srAvail then call KV 'Outstanding replies', 'NONE MATCHED'
else call KV 'Outstanding replies', 'UNAVAILABLE'

if grsWaiting then call KV 'GRS wait', 'YES'
else if grsAvail & grsNoWait then call KV 'GRS wait', 'NO WAIT REPORTED'
else if grsAvail then call KV 'GRS wait', 'NO TARGET WAIT PARSED'
else call KV 'GRS wait', 'UNAVAILABLE / NOT AUTHORIZED'

if limitNote <> '' then call KV 'Sampling note', limitNote
return


TargetReport:
call Section 'TARGET JOB'

call KV 'Job name', targetJob
call KV 'Job ID', ValueOr(targetJobid,ValueOr(stJobid,'N/A'))
call KV 'System', ValueOr(targetSys,ValueOr(stSys,'N/A'))
call KV 'ASID', ValueOr(targetAsid,'N/A')
call KV 'Status', ValueOr(targetStatus,ValueOr(stStatus,'N/A'))
call KV 'Queue', ValueOr(stQueue,'N/A')
call KV 'Phase', ValueOr(stPhase,'N/A')
call KV 'Owner', ValueOr(targetOwner,ValueOr(stOwner,'N/A'))
call KV 'Step', ValueOr(targetStep,'N/A')
call KV 'ProcStep', ValueOr(targetProcStep,'N/A')
call KV 'Service class', ValueOr(targetSrvClass,ValueOr(stSrvClass,'N/A'))
call KV 'Subsystem mode', ValueOr(stMode,'N/A')

if targetResolved then
  call KV 'DA target resolution', 'UNIQUE ACTIVE MATCH'
else
  call KV 'DA target resolution', 'NOT AVAILABLE / NOT FOUND'

return


CollectionReport:
call Section 'COLLECTION STATUS'

call KV 'ST RC', stRc
call KV 'ST rows returned', stRows
call KV 'ST exact matches', stMatchCount
call KV 'DA RC', daRc
call KV 'DA rows returned', daRows
call KV 'DA target candidates', candidateCount
call KV 'SDSF request model', 'MINIMAL VARIABLES / SHARED STEMS'

if stCols <> '' then
  call KV 'ST columns returned', ShortText(stCols,88)

if daCols <> '' then
  call KV 'DA columns returned', ShortText(daCols,88)

if targetResolved = 0 then do
  call Emit ''
  call Emit 'LONGDIAG did not resolve an active DA row for the target.'
  call Emit 'The collection details below are included so the reason is'
  call Emit 'visible instead of silently producing only N/A values.'

  if stDisplay <> '' then do
    call Emit ''
    call Emit 'ST SDSF criteria:'
    call Emit ' 'ShortText(stDisplay,120)
  end

  if daDisplay <> '' then do
    call Emit ''
    call Emit 'DA SDSF criteria:'
    call Emit ' 'ShortText(daDisplay,120)
  end

  if stMsg <> '' then do
    call Emit ''
    call Emit 'ST SDSF message:'
    call Emit ' 'ShortText(stMsg,120)
  end

  if daMsg <> '' then do
    call Emit ''
    call Emit 'DA SDSF message:'
    call Emit ' 'ShortText(daMsg,120)
  end
end

return


DelayReport:
call Section 'SDSF ST / JES2 DELAY STATUS'

if stAvail = 0 then do
  call Emit 'SDSF ST data was unavailable for this invocation.'
  return
end

if stMatchCount = 0 then do
  call Emit 'No matching ST row was returned for the target.'
  return
end

call KV 'ST matching rows', stMatchCount

if stDelaySupported then do
  call KV 'Dly', ValueOr(stDly,'<blank>')
  call KV 'DelayRsn', ValueOr(stDelayRsn,'<blank>')
  call Emit ''
  call Emit 'A populated DelayRsn is positive JES2 delay evidence.'
  call Emit 'A blank DelayRsn does NOT prove that the job is healthy.'
end
else do
  call Emit 'The DELAY/DELAYRSN columns were not returned by this SDSF'
  call Emit 'level or authorization. LONGDIAG continued with fallback'
  call Emit 'runtime, WTOR, GRS and system-context analysis.'
end

return


ProgressReport:
call Section 'PROGRESS SAMPLING'

if validSamples = 0 then do
  call Emit 'No usable DA runtime samples were collected.'
  return
end

call Emit 'Smp Time       CPU-Time        EXCP      CPU%       SIO    Paging'
call Emit copies('-',73)

do prI = 1 to sampleCount
  if sampleOK.prI then do
    ln = right(prI,3) left(sampleTime.prI,11) ,
         right(ValueOr(sampleCpu.prI,'N/A'),12) ,
         right(ValueOr(sampleExcp.prI,'N/A'),10) ,
         right(ValueOr(sampleCpuPct.prI,'N/A'),9) ,
         right(ValueOr(sampleSio.prI,'N/A'),9) ,
         right(ValueOr(samplePaging.prI,'N/A'),9)
  end
  else do
    ln = right(prI,3) left('N/A',11) right('N/A',12) ,
         right('N/A',10) right('N/A',9) right('N/A',9) ,
         right('N/A',9)
  end

  call Emit ln
end

call Emit ''
call KV 'Progress assessment', progressState

if cpuDelta >= 0 then call KV 'CPU-Time delta', FormatNum(cpuDelta)
else call KV 'CPU-Time delta', 'N/A'

if excpDelta >= 0 then call KV 'EXCP delta', FormatNum(excpDelta)
else call KV 'EXCP delta', 'N/A'

call Emit ''
call Emit 'CPU-Time and EXCP movement during a short sample establishes'
call Emit 'activity, not whether the application is processing efficiently.'

return


SystemReport:
call Section 'TARGET SYSTEM CONTEXT'

if sysAvail = 0 then do
  call Emit 'SDSF SYS context was unavailable for the target system.'
  return
end

call KV 'System', ValueOr(targetSys,localSys)
call KV 'System CPU%', FormatMaybePct(sysCpu)
call KV 'System paging rate', FormatMaybe(sysPage)
call KV 'Real storage used%', FormatMaybePct(sysRealPct)
call KV 'Aux storage used%', FormatMaybePct(sysAuxPct)
call KV 'System SIO rate', FormatMaybe(sysSio)

if sysCpu >= 90 then do
  call Emit ''
  call Emit 'Signal: system CPU is currently high.'
  if currentCpuPct >= 0 & currentCpuPct < 5 then
    call Emit 'The target itself is showing little CPU at this snapshot.'
end

if sysRealPct >= 95 & sysPage > 0 then do
  call Emit ''
  call Emit 'Signal: low real-storage headroom exists with paging activity.'
end

return


OperatorReport:
call Section 'OUTSTANDING OPERATOR REQUESTS'

if srAvail = 0 then do
  call Emit 'SDSF SR was unavailable or not authorized.'
  return
end

if wtorCount = 0 then do
  call Emit 'No outstanding SR entry matched this job at collection time.'
  return
end

call KV 'Matching requests', wtorCount
call Emit ''

show = wtorCount
if show > maxWtorLines then show = maxWtorLines

do orI = 1 to show
  call Emit ' 'wtorLine.orI
end

if wtorCount > show then
  call Emit ' Additional matching SR entries were omitted from this report.'

call Emit ''
call Emit 'LONGDIAG never issues a reply. Review the exact message and'
call Emit 'application/operations procedure before any operator action.'

return


GRSReport:
call Section 'GRS / RESOURCE DEPENDENCY'

if grsTried = 0 then do
  call Emit 'GRS analysis was not attempted because the executing system'
  call Emit 'for the target could not be determined.'
  return
end

if grsAvail = 0 then do
  call KV 'GRS command status', 'UNAVAILABLE / NOT AUTHORIZED'
  call KV 'Command RC', grsRc
  call Emit ''
  call Emit 'LONGDIAG continued without GRS evidence.'
  return
end

if grsDeadlock then call KV 'Deadlock text', 'DETECTED IN RESPONSE'
if grsWaiting then call KV 'Target waiting', 'YES'
else if grsNoWait then call KV 'Target waiting', 'NO WAIT REPORTED'
else call KV 'Target waiting', 'NO WAIT PARSED'

if grsWaitTime <> '' then call KV 'Reported wait', grsWaitTime
if grsQname <> '' then call KV 'QNAME', grsQname
if grsRname <> '' then call KV 'RNAME', ShortText(grsRname,90)
if grsBlocker <> '' then call KV 'Possible holder', grsBlocker
if grsBlockerSys <> '' then call KV 'Holder system', grsBlockerSys

if grsHitCount > 0 then do
  call Emit ''
  call Emit 'Selected GRS response lines:'
  do grI = 1 to grsHitCount
    call Emit ' 'grsHit.grI
  end
end

return


CauseReport:
call Section 'FINDINGS / INTERPRETATION'
call KV 'Diagnosis', diagnosis
call KV 'Confidence', confidence
call Emit ''

select
  when wtorCount > 0 then do
    call Emit 'The strongest evidence is an outstanding operator request'
    call Emit 'associated with the target job.'
  end

  when grsWaiting then do
    call Emit 'GRS reports the target as waiting on a managed resource.'
    if grsBlocker <> '' then
      call Emit 'Investigate the reported holder before recovery action.'
  end

  when strip(stDelayRsn) <> '' then do
    call Emit 'JES2 supplied a current delay reason through SDSF ST.'
    call Emit 'LONGDIAG preserved the literal DelayRsn above.'
  end

  when progressState = 'ACTIVE / PROGRESS OBSERVED' then do
    call Emit 'The target accumulated CPU time and/or EXCPs during the'
    call Emit 'observation interval, so it is not completely stationary.'
  end

  when left(progressState,17) = 'PROGRESS OBSERVED' then do
    call Emit 'The step/procstep changed during observation, which is'
    call Emit 'direct evidence that job execution advanced.'
  end

  when progressState = 'NO CPU OR EXCP PROGRESS OBSERVED' then do
    call Emit 'No CPU-Time or EXCP movement was observed in the short'
    call Emit 'sample. This is a stall signal, not proof of a hang.'
  end

  otherwise do
    call Emit 'Available system-level evidence does not establish a'
    call Emit 'specific cause for the extended elapsed time.'
  end
end

if currentCpuPct >= 50 then do
  call Emit ''
  call Emit 'The target currently shows significant CPU use. LONGDIAG'
  call Emit 'cannot prove a CPU loop without task/module-level evidence.'
end

return


Recommendations:
call Section 'RECOMMENDED DEBUGGING / POSSIBLE RESOLUTION'

select
  when wtorCount > 0 then do
    call Emit '1. Review the exact outstanding request shown above.'
    call Emit '2. Confirm the required reply with the owning application'
    call Emit '   or operations procedure before responding.'
    call Emit '3. Do not cancel the job merely because a WTOR is present.'
  end

  when grsWaiting then do
    call Emit '1. Investigate the holder/resource shown by GRS.'
    call Emit '2. For a full dependency-chain report, run:'
    call Emit '      TSO ENQDIAG JOB 'targetJob' SYS 'targetSys
    call Emit '3. Determine whether the holder is progressing before any'
    call Emit '   CANCEL/FORCE decision.'
  end

  when diagnosis = 'POSSIBLE CPU STARVATION / SYSTEM CPU PRESSURE' then do
    call Emit '1. Run TSO CPUDIAG to identify persistent CPU consumers.'
    call Emit '2. Check whether high CPU is peak workload or abnormal.'
    call Emit '3. Do not assume the target job itself is the CPU problem.'
  end

  when diagnosis = 'POSSIBLE STORAGE / PAGING PRESSURE' then do
    call Emit '1. Run TSO STORDIAG to confirm system storage pressure.'
    call Emit '2. Correlate paging and real-storage headroom over time.'
    call Emit '3. Avoid storage changes based only on this short sample.'
  end

  when left(diagnosis,13) = 'CPU-INTENSIVE' then do
    call Emit '1. The job is progressing but is CPU intensive.'
    call Emit '2. Run TSO CPUDIAG if system CPU is also elevated.'
    call Emit '3. If a loop is suspected, obtain product/task-level'
    call Emit '   evidence; high CPU alone does not prove a loop.'
  end

  when pos('PROGRESSING',diagnosis) > 0 | ,
       diagnosis = 'JOB IS MAKING PROGRESS' then do
    call Emit '1. The job is currently making measurable progress.'
    call Emit '2. Compare current elapsed time with normal/history data.'
    call Emit '3. Review the active step and application-specific work.'
    call Emit '4. Do not treat long elapsed time alone as a hang.'
  end

  when strip(stDelayRsn) <> '' then do
    call Emit '1. Start with the literal ST DelayRsn shown above.'
    call Emit '2. Correlate it with the current step, JES2 messages and'
    call Emit '   the owning subsystem/application.'
    call Emit '3. Use the relevant specialist diagnostic if indicated:'
    call Emit '      CPU -> CPUDIAG   Storage -> STORDIAG'
    call Emit '      ENQ -> ENQDIAG   XCF -> XCFDIAG'
  end

  otherwise do
    call Emit '1. Review the current step and joblog/application messages.'
    call Emit '2. Check product/subsystem dependencies such as DB2, MQ,'
    call Emit '   TCP/IP, CICS, HSM, tape or an external service.'
    call Emit '3. Compare this run with normal historical elapsed time.'
    call Emit '4. If the condition persists, preserve evidence before'
    call Emit '   disruptive recovery.'
  end
end

return



CommandReference:
call Section 'COMMANDS USED / NEXT COMMANDS'
call Emit 'DISPLAY / DIAGNOSTIC COMMANDS'
call Emit ''

cmdSys = targetSys
if cmdSys = '' then cmdSys = localSys

call Emit "  $DJ'"targetJob"',LONG"
call Emit '      JES2 long-format display for the target job.'
call Emit ''
call Emit '  D R,L'
call Emit '      Display outstanding replies/action requests.'
call Emit ''
call Emit '  D GRS,ANALYZE,DEPENDENCY,SYSTEM='cmdSys',JOBNAME='targetJob','
call Emit '    COUNT='analysisCount',DETAIL'
call Emit '      Follow GRS dependencies for the target job.'
call Emit ''
call Emit '  TSO CPUDIAG'
call Emit '      Use when CPU pressure/starvation is suspected.'
call Emit ''
call Emit '  TSO STORDIAG'
call Emit '      Use when storage/paging pressure is suspected.'
call Emit ''
call Emit '  TSO ENQDIAG JOB 'targetJob' SYS 'cmdSys
call Emit '      Use for deeper ENQ/resource dependency analysis.'
call Emit ''
call Emit 'MODIFYING / RECOVERY COMMANDS'
call Emit ''
call Emit '  LONGDIAG does not automatically issue modifying commands.'
call Emit '  A modifying command is shown only as conditional guidance'
call Emit '  after the cause is confirmed and site approval is obtained.'
call Emit ''
call Emit '  R <replyid>,<approved-response>'
call Emit '      Conditional only: respond to a confirmed WTOR.'
call Emit ''
call Emit '  C <jobname>'
call Emit '      Last-resort conditional example only. Use only when the'
call Emit '      owning team confirms that cancellation is safe.'
return


SampleReport:
call Emit ''
call Emit copies('=',78)
call Emit ' LONGDIAG - RUNNING-LONG JOB DIAGNOSTIC UTILITY'
call KV 'Version', ldVersion
call KV 'Author', 'SARATH S'
call KV 'Report mode', 'SAMPLE / DEMONSTRATION'
call KV 'Date', DisplayDate()
call KV 'Time', time('N')
call KV 'System', localSys
call Emit copies('=',78)
call Emit ''
call Emit '*** SAMPLE REPORT - WORKLOAD VALUES BELOW ARE FICTIONAL ***'
call Emit 'The system name/date/time are obtained from the system where'
call Emit 'LONGDIAG SAMPLE is invoked. No SDSF collection or modifying'
call Emit 'command is performed while generating this sample.'
call Emit ''

call Section 'TRIAGE SUMMARY'
call KV 'Severity', 'HIGH'
call KV 'Primary diagnosis', 'GRS / DATA SET CONTENTION'
call KV 'Confidence', 'HIGH'
call KV 'Job progress', 'NO CPU OR EXCP PROGRESS OBSERVED'
call KV 'ST DelayRsn', 'Waiting for resource / data set'
call KV 'Outstanding replies', 'NONE MATCHED'
call KV 'GRS wait', 'YES'
call Emit ''
call Emit 'Interpretation: the sample job is stationary because it is'
call Emit 'waiting for a resource held by another job. CPU/storage are'
call Emit 'not the primary cause in this example.'

call Section 'TARGET JOB'
call KV 'Job name', 'SAMPJOB'
call KV 'Job ID', 'JOB12345'
call KV 'System', localSys
call KV 'ASID', '02F1'
call KV 'Status', 'EXECUTION'
call KV 'Queue', 'EXECUTION'
call KV 'Phase', 'EXECUTING'
call KV 'Owner', 'SAMPLEID'
call KV 'Step', 'STEP050'
call KV 'ProcStep', 'PAYPROC'
call KV 'Start', '20-Aug-2026 20:14:03'
call KV 'Elapsed', '02:16:08'
call KV 'Service class', 'BATCHMED'
call KV 'DA target resolution', 'UNIQUE ACTIVE MATCH'

call Section 'SDSF ST / JES2 DELAY STATUS'
call KV 'Dly', 'YES'
call KV 'DelayRsn', 'Waiting for resource / data set'
call Emit ''
call Emit 'Sample interpretation: JES2 is reporting a delay clue.'
call Emit 'LONGDIAG does not treat DelayRsn alone as root-cause proof.'

call Section 'PROGRESS SAMPLING'
call Emit 'Smp Time       CPU-Time        EXCP      CPU%       SIO    Paging'
call Emit copies('-',73)
call Emit '  1 20:30:10          82.31      18421      0.1       0.0      0.0'
call Emit '  2 20:30:15          82.31      18421      0.0       0.0      0.0'
call Emit ''
call KV 'Progress assessment', 'NO CPU OR EXCP PROGRESS OBSERVED'
call KV 'CPU-Time delta', '0.00'
call KV 'EXCP delta', '0'
call Emit ''
call Emit 'Interpretation: the address space did not accumulate CPU time'
call Emit 'or EXCP activity during the short observation interval.'

call Section 'TARGET SYSTEM CONTEXT'
call KV 'System', localSys
call KV 'System CPU%', '61.80%'
call KV 'System paging rate', '0.00'
call KV 'Real storage used%', '72.40%'
call KV 'Aux storage used%', '10.20%'
call KV 'System SIO rate', '1450.00'
call Emit ''
call Emit 'Interpretation: this sample does not show system-wide CPU or'
call Emit 'storage pressure sufficient to explain the target delay.'

call Section 'OUTSTANDING OPERATOR REQUESTS'
call Emit 'No outstanding SR entry matched SAMPJOB in this sample.'
call Emit ''
call Emit 'Interpretation: a WTOR/operator wait is not the leading cause.'

call Section 'GRS / RESOURCE DEPENDENCY'
call KV 'Target waiting', 'YES'
call KV 'Reported wait', '00:41:22'
call KV 'QNAME', 'SYSDSN'
call KV 'RNAME', 'SAMPLE.APP.MASTER'
call KV 'Possible holder', 'HOLDJOB'
call KV 'Holder system', localSys
call Emit ''
call Emit 'Sample dependency chain:'
call Emit ''
call Emit '  SAMPJOB'
call Emit '     |'
call Emit '     +-- waits for SYSDSN / SAMPLE.APP.MASTER'
call Emit '             |'
call Emit '             +-- held by HOLDJOB on 'localSys
call Emit ''
call Emit 'Selected sample GRS response lines:'
call Emit '  GRS ANALYSIS - SAMPLE DATA'
call Emit '  SYSTEM: 'localSys' JOBNAME: SAMPJOB'
call Emit '  WAITING 00:41:22 FOR SYSDSN SAMPLE.APP.MASTER'
call Emit '  SYSTEM: 'localSys' JOBNAME: HOLDJOB'

call Section 'FINDINGS / INTERPRETATION'
call KV 'Diagnosis', 'GRS / DATA SET CONTENTION'
call KV 'Confidence', 'HIGH'
call Emit ''
call Emit 'The sample target has no measurable CPU or EXCP progress, while'
call Emit 'GRS evidence shows that SAMPLE.APP.MASTER is owned by HOLDJOB.'
call Emit 'This makes the resource dependency the strongest explanation'
call Emit 'for the extended runtime in this demonstration.'
call Emit ''
call Emit 'Important: a real report may instead classify CPU starvation,'
call Emit 'heavy but valid CPU/I/O processing, WTOR wait, storage pressure,'
call Emit 'JES2 DelayRsn evidence, or an unidentified application wait.'

call Section 'RECOMMENDED DEBUGGING / POSSIBLE RESOLUTION'
call Emit '1. Confirm the dependency with targeted GRS analysis.'
call Emit '2. Diagnose HOLDJOB before taking action against it.'
call Emit '3. Determine whether HOLDJOB is progressing normally or is'
call Emit '   itself blocked by another resource.'
call Emit '4. If HOLDJOB is legitimate active work, allow it to complete.'
call Emit '5. If the owning team confirms HOLDJOB is hung and cancellation'
call Emit '   is safe, follow the site incident/change procedure.'
call Emit '6. Re-run LONGDIAG/ENQDIAG after recovery to verify the wait'
call Emit '   has cleared and SAMPJOB resumes progress.'

call Section 'COMMANDS USED / NEXT COMMANDS'
call Emit 'DISPLAY / DIAGNOSTIC COMMANDS'
call Emit ''
call Emit "  $DJ'SAMPJOB',LONG"
call Emit '      Display JES2 long-format information for the sample job.'
call Emit ''
call Emit '  D R,L'
call Emit '      Check outstanding replies/action requests.'
call Emit ''
call Emit '  D GRS,ANALYZE,DEPENDENCY,SYSTEM='localSys',JOBNAME=SAMPJOB,'
call Emit '    COUNT=10,DETAIL'
call Emit '      Analyze the target resource dependency.'
call Emit ''
call Emit '  D GRS,ANALYZE,DEPENDENCY,SYSTEM='localSys',JOBNAME=HOLDJOB,'
call Emit '    COUNT=10,DETAIL'
call Emit '      Determine whether the sample holder has another dependency.'
call Emit ''
call Emit '  TSO ENQDIAG JOB SAMPJOB SYS 'localSys
call Emit '      Produce deeper ENQ/resource dependency analysis.'
call Emit ''
call Emit 'MODIFYING / RECOVERY COMMANDS - CONDITIONAL EXAMPLES'
call Emit ''
call Emit '  LONGDIAG NEVER issues these commands automatically.'
call Emit ''
call Emit '  R <replyid>,<approved-response>'
call Emit '      Use only when a real diagnosis identifies a WTOR and the'
call Emit '      correct response is confirmed by procedure/owner.'
call Emit ''
call Emit '  C HOLDJOB'
call Emit '      SAMPLE ONLY. Last-resort example if the owning team has'
call Emit '      confirmed the blocker is hung and cancellation is safe.'
call Emit ''
call Emit 'No modifying command is required merely because a job is long.'

call Section 'IMPORTANT NOTES'
call Emit '  * All job names, job IDs, ASIDs, data sets, timings, CPU, I/O'
call Emit '    and dependency values in this SAMPLE report are fictional.'
call Emit '  * The displayed system name is the real invocation system.'
call Emit '  * Commands are examples of what a live LONGDIAG report may'
call Emit '    recommend; SAMPLE mode does not execute them.'
call Emit '  * A live LONGDIAG run remains read-only.'
call Emit '  * JY and JT are not required.'
call Emit '  * A blank DelayRsn never proves that the job is healthy.'
call Emit '  * Cancellation/reply commands always require human validation'
call Emit '    and the applicable site authorization/change procedure.'

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


Notes:
call Section 'IMPORTANT NOTES'

call Emit '  * LONGDIAG is read-only.'
call Emit '  * JY and JT are not required.'
call Emit '  * ST DelayRsn is used when SDSF returns that JES2 column.'
call Emit '  * A blank DelayRsn does not mean the job is healthy.'
call Emit '  * DA CPU%, SIO and paging values are point-in-time signals.'
call Emit '  * CPU-Time/EXCP deltas are used to establish short-term'
call Emit '    progress; no movement is evidence, not proof, of a hang.'
call Emit '  * CPU/storage classifications use LONGDIAG triage heuristics;'
call Emit '    they are not IBM-defined severity thresholds.'
call Emit '  * GRS analysis is targeted to the job/system and is display'
call Emit '    only. Failure or lack of authority does not stop the run.'
call Emit '  * SDSF/SAF/OPERCMDS scope determines what can be observed.'
call Emit '  * LONGDIAG does not issue FILTER OFF, DEST, SORT or SET DELAY'
call Emit '    while collecting ordinary ST/DA/SYS/SR panel data.'
call Emit '  * ST/DA row counts and SDSF messages are retained in the'
call Emit '    report when target discovery fails.'
call Emit '  * SDSF column stems are read directly from the shared REXX'
call Emit '    variable pool; JY/JT panels are still not required.'
call Emit '  * Separate JES spool can limit joblog visibility, but does'
call Emit '    not prevent the live checks used by this version.'
call Emit '  * LONGDIAG does not automatically start RMF or Runtime'
call Emit '    Diagnostics and does not require either product.'
call Emit '  * No CANCEL, FORCE, REPLY, DEQ, WLM, JES2 or storage change'
call Emit '    is issued.'

return


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


CandidateReport:
call Emit 'JobID     System   ASID     Status               Elapsed'
call Emit copies('-',62)

do crI = 1 to candidateCount
  ln = left(candidateJobid.crI,10) ,
       left(candidateSys.crI,8) ,
       left(candidateAsid.crI,8) ,
       left(candidateStatus.crI,20) ,
       left(candidateElapsed.crI,12)
  call Emit ln
end
return


HelpReport:
call Emit ''
call Emit copies('=',78)
call Emit ' LONGDIAG - README / HELP'
call Emit copies('=',78)
call Emit ''
call Emit 'NAME'
call Emit '  LONGDIAG - z/OS running-long job diagnostic utility.'
call Emit ''
call Emit 'PURPOSE'
call Emit '  LONGDIAG is a read-only first-response utility for an active'
call Emit '  batch job or started task that appears to be taking longer'
call Emit '  than expected. It asks two questions: is the job progressing,'
call Emit '  and what current system condition may be delaying it?'
call Emit ''
call Emit 'WHEN TO USE'
call Emit '  * A batch job is running longer than its normal elapsed time.'
call Emit '  * A started task appears active but is not completing work.'
call Emit '  * You need to distinguish processing from a wait/stall.'
call Emit ''
call Emit 'WHAT LONGDIAG CHECKS'
call Emit '  1. SDSF ST identity, Dly and DelayRsn when available.'
call Emit '  2. Repeated SDSF DA CPU-Time and EXCP progress samples.'
call Emit '  3. Target CPU%, SIO and paging data when SDSF supplies them.'
call Emit '  4. Current system CPU/storage context from SDSF SYS.'
call Emit '  5. Outstanding operator requests through SDSF SR.'
call Emit '  6. One targeted D GRS,ANALYZE,DEPENDENCY display.'
call Emit ''
call Emit 'TARGET DISCOVERY'
call Emit '  LONGDIAG starts with SDSF REXX input variables undefined and'
call Emit '  sets only PREFIX/OWNER for initial discovery. It resolves the'
call Emit '  job in ST first and scopes DA by SYSNAME only when the target'
call Emit '  execution system is known.'
call Emit ''
call Emit 'JY / JT'
call Emit '  LONGDIAG does not require SDSF JY or JT. If those panels are'
call Emit '  unavailable at a site, normal LONGDIAG processing continues.'
call Emit ''
call Emit 'DELAYRSN'
call Emit '  A populated ST DelayRsn is useful JES2 delay evidence.'
call Emit '  A blank DelayRsn is not treated as proof that a job is healthy.'
call Emit ''
call Emit 'SYNTAX'
call Emit '  TSO LONGDIAG jobname'
call Emit '  TSO LONGDIAG jobname JOBID jobid'
call Emit '  TSO LONGDIAG jobname SYS sysname'
call Emit '  TSO LONGDIAG jobname SYS sysname ASID asid'
call Emit '  TSO LONGDIAG jobname SAMPLES n INTERVAL n'
call Emit '  TSO LONGDIAG HELP'
call Emit '  TSO LONGDIAG SAMPLE'
call Emit ''
call Emit 'SAMPLING'
call Emit '  Default: 2 DA samples, 5 seconds apart.'
call Emit '  SAMPLES range: 1-3.'
call Emit '  INTERVAL range: 2-30 seconds.'
call Emit '  If USS sleep support is unavailable, LONGDIAG falls back to'
call Emit '  one snapshot instead of failing.'
call Emit ''
call Emit 'SAMPLE MODE'
call Emit '  TSO LONGDIAG SAMPLE generates a full demonstration report.'
call Emit '  Workload/job/resource values are fictional. The invocation'
call Emit '  system name/date/time and command forms remain realistic.'
call Emit '  SAMPLE mode does not query SDSF or issue system commands.'
call Emit ''
call Emit 'OUTPUT'
call Emit '  Every successful output is retained as a unique cataloged'
call Emit '  data set:'
call Emit ''
call Emit '    <USERID>.LONGDIAG.Dyymmdd.Thhmmss'
call Emit '    <USERID>.LONGDIAG.SAMPLE.Dyymmdd.Thhmmss'
call Emit '    <USERID>.LONGDIAG.HELP.Dyymmdd.Thhmmss'
call Emit ''
call Emit '  Saved output remains cataloged after ISPF Browse is exited.'
call Emit ''
call Emit 'SAFETY / EXPECTED IMPACT'
call Emit '  LONGDIAG performs short SDSF reads, two small runtime samples'
call Emit '  by default, and one job-targeted GRS display when permitted.'
call Emit '  It does not run broad continuous monitors or start RMF.'
call Emit '  Expected diagnostic impact for one target is low.'
call Emit ''
call Emit '  LONGDIAG NEVER automatically issues:'
call Emit '    CANCEL, FORCE, REPLY, DEQ, SETGRS, WLM changes, JES2'
call Emit '    modifying commands, storage changes or configuration updates.'
call Emit ''
call Emit 'IMPORTANT LIMITATIONS'
call Emit '  * Results depend on SDSF/SAF/OPERCMDS authorization.'
call Emit '  * A short no-progress sample does not prove an application hang.'
call Emit '  * High CPU does not by itself prove a loop.'
call Emit '  * Without task-level data, LONGDIAG does not name a looping TCB.'
call Emit '  * External waits such as DB2, MQ, TCP/IP or application service'
call Emit '    dependencies may require product-specific investigation.'
call Emit '  * LONGDIAG diagnoses current state; it does not maintain a'
call Emit '    historical baseline of normal job elapsed times.'
call Emit ''
call Emit 'RECOMMENDED FIRST USE'
call Emit '  TSO LONGDIAG SAMPLE'
call Emit '      Preview the report format and interpretations.'
call Emit ''
call Emit '  TSO LONGDIAG jobname'
call Emit '      Diagnose a real active job or started task.'
call Emit ''
call Emit '  If duplicate active instances exist, LONGDIAG lists them and'
call Emit '  asks you to rerun with JOBID, SYS or ASID rather than guessing.'
call Emit ''
call Emit copies('=',78)
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
out = ''
do emI = 1 to arg()
  piece = arg(emI)
  if emI = 1 then out = piece
  else out = out piece
end
rptCount = rptCount + 1
rpt.rptCount = out
rpt.0 = rptCount
return


GetVar:
/* Do not use PROCEDURE here. SDSF creates column stems in the caller */
/* variable pool (for example JNAME.1, JOBID.1, CPU.1). A PROCEDURE  */
/* scope would hide those stems and make every lookup return default. */
parse arg gvName, gvRow, gvDefault
gvExpr = gvName'.'gvRow

if symbol(gvExpr) = 'VAR' then do
  interpret 'gvValue='gvExpr
  gvValue = strip(gvValue)
  if gvValue <> '' then return gvValue
end

return gvDefault


SaveSdsfMessage:
sm = ''

if symbol('ISFMSG') = 'VAR' then sm = strip(isfmsg)

if symbol('ISFMSG2.0') = 'VAR' then do
  do smI = 1 to isfmsg2.0
    smLine = strip(isfmsg2.smI)
    if smLine = '' then iterate
    if sm = '' then sm = smLine
    else sm = sm'; 'smLine
    if length(sm) > 240 then leave
  end
end

return sm


ValueOr:
procedure
parse arg voValue, voDefault
voValue = strip(voValue)
if voValue = '' then return voDefault
return voValue


StripHex:
procedure
parse upper arg sh
sh = strip(sh)

if left(sh,2) = "X'" then sh = substr(sh,3)
if right(sh,1) = "'" then sh = left(sh,length(sh)-1)
if left(sh,2) = '0X' then sh = substr(sh,3)

return strip(sh)


MetricNumber:
procedure
numeric digits 20
parse arg mn
mn = strip(mn)

if mn = '' | mn = '-' | translate(mn) = 'N/A' then return -1

if pos(':',mn) > 0 then return DurationToSeconds(mn)

mn = translate(mn,'  ',',%')
mn = space(mn,0)

if datatype(mn,'N') then return mn

u = translate(mn)
mult = 1
cut = 0

if right(u,2) = 'KB' then do
  mult = 1024
  cut = 2
end
else if right(u,2) = 'MB' then do
  mult = 1024 * 1024
  cut = 2
end
else if right(u,2) = 'GB' then do
  mult = 1024 * 1024 * 1024
  cut = 2
end
else if right(u,1) = 'K' then do
  mult = 1000
  cut = 1
end
else if right(u,1) = 'M' then do
  mult = 1000000
  cut = 1
end
else if right(u,1) = 'G' then do
  mult = 1000000000
  cut = 1
end

if cut > 0 then do
  base = left(mn,length(mn)-cut)
  if datatype(base,'N') then return base * mult
end

return -1


DurationToSeconds:
procedure
numeric digits 20
parse arg ds
ds = strip(ds)

if ds = '' then return -1

days = 0
hy = pos('-',ds)

if hy > 0 then do
  dpart = left(ds,hy-1)
  if datatype(dpart,'N') then days = dpart
  ds = substr(ds,hy+1)
end

parts = translate(ds,' ',':')
wc = words(parts)

if wc = 3 then do
  hh = word(parts,1)
  mm = word(parts,2)
  ss = word(parts,3)
end
else if wc = 2 then do
  hh = 0
  mm = word(parts,1)
  ss = word(parts,2)
end
else if wc = 1 then do
  hh = 0
  mm = 0
  ss = word(parts,1)
end
else return -1

if datatype(hh,'N') = 0 then return -1
if datatype(mm,'N') = 0 then return -1
if datatype(ss,'N') = 0 then return -1

return days*86400 + hh*3600 + mm*60 + ss


FormatNum:
procedure
numeric digits 20
parse arg fn
if datatype(fn,'N') = 0 then return 'N/A'
return strip(format(fn,12,2))


FormatMaybe:
procedure
parse arg fm
if datatype(fm,'N') = 0 | fm < 0 then return 'N/A'
return strip(format(fm,12,2))


FormatMaybePct:
procedure
parse arg fp
if datatype(fp,'N') = 0 | fp < 0 then return 'N/A'
return strip(format(fp,7,2))'%'


ShortText:
procedure
parse arg stText, stLen
stText = strip(stText)
if length(stText) <= stLen then return stText
if stLen <= 3 then return left(stText,stLen)
return left(stText,stLen-3)'...'


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


SleepSeconds:
parse arg slSec
if ussOn = 0 then return
if slSec <= 0 then return
Address SYSCALL 'sleep' slSec
return


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

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

dqual = 'D'
dqual = dqual || substr(date('S'),3,6)

tqual = 'T'
tval = time('N')
tqual = tqual || substr(tval,1,2)
tqual = tqual || substr(tval,4,2)
tqual = tqual || substr(tval,7,2)

if mode = 'HELP' then
  rptDsn = uid'.LONGDIAG.HELP.'dqual'.'tqual
else if mode = 'SAMPLE' then
  rptDsn = uid'.LONGDIAG.SAMPLE.'dqual'.'tqual
else
  rptDsn = uid'.LONGDIAG.'dqual'.'tqual

ddn = 'LDRPT001'

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
prRc = rc

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

/* Record the retained location inside the report itself. */
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)'
prWrc = rc
Address TSO 'FREE F('ddn')'

if prWrc <> 0 then do
  Address TSO "DELETE '"rptDsn"'"
  say 'LONGDIAG: Report write failed; incomplete data set deleted.'
  do prI = 1 to rptCount
    say rpt.prI
  end
  return
end

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

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

if prBrc > 4 then do
  say 'LONGDIAG: ISPF Browse failed RC='prBrc
  say "Saved report remains cataloged as '"rptDsn"'"
  do prI = 1 to rptCount
    say rpt.prI
  end
end

return

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

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

return


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