The latest Technology Refresh for IBM i 7.3, TR6, has seen two new subfields added to RPG's Program Data Structure. This data strucutre provides me with a wealth information about the status of the program while it is running, and when it errors.
I always add the Program Status Data Structure, PSDS, to all of my RPG programs. I can dump the program and learn a lot of what happened from the information contains within the PSDS.
Rather than manually entering the same data structure into every program, I have my PSDS in a member I just copy, or include, it into the source members of others.
Below is my definition for the PSDS. If you are creating your own you can give the data structure and subfields whatever names you like.
**free dcl-ds Pgm psds qualified ; Proc char(10) ; // Module or main procedure name StsCde zoned(5) ; // Status code PrvStsCde zoned(5) ; // Previous status SrcLineNbr char(8) ; // Source line number Routine char(8) ; // Name of the RPG routine Parms zoned(3) ; // Number of parms passed to program ExceptionType char(3) ; // Exception type ExceptionNbr char(4) ; // Exception number Exception char(7) samepos(ExceptionType) ; Reserved1 char(4) ; // Reserved MsgWrkArea char(30) ; // Message work area PgmLib char(10) ; // Program library ExceptionData char(80) ; // Retrieved exception data Rnx9001Exception char(4) ; // Id of exception that caused RNX9001 LastFile1 char(10) ; // Last file operation occurred on Unused1 char(6) ; // Unused DteEntered char(8) ; // Date entered system StrDteCentury zoned(2) ; // Century of job started date LastFile2 char(8) ; // Last file operation occurred on LastFileSts char(35) ; // Last file used status information JobName char(10) ; // Job name JobUser char(10) ; // Job user JobNbr zoned(6) ; // Job number StrDte zoned(6) ; // Job started date PgmDte zoned(6) ; // Date of program running PgmTime zoned(6) ; // Time of program running CompileDte char(6) ; // Date program was compiled CompileTime char(6) ; // Time program was compiled CompilerLevel char(4) ; // Level of compiler SrcFile char(10) ; // Source file name SrcLib char(10) ; // Source file library SrcMbr char(10) ; // Source member name ProcPgm char(10) ; // Program containing procedure ProcMod char(10) ; // Module containing procedure SrcLineNbrBin bindec(2) ; // Source line number as binary LastFileStsBin bindec(2) ; // Source id matching positions 228-235 User char(10) ; // Current user ExtErrCode int(10) ; // External error code IntoElements int(20) ; // Elements set by XML-INTO or DATA-INTO (7.3) InternalJobId char(16) ; // Internal job id (7.3 TR6) SysName char(8) ; // System name (7.3 TR6) end-ds ; |
Two things to note are:
- As the data structure has been written in totally free format RPG I have to start the source member with **FREE. If I don't do so when the member is copied into the other source member by the RPG compiler it copies the member starting in the seventh column of the source member.
- I have used the SAMEPOS keyword to combine the Exception Type and Exception Number subfields.
The two new subfields are the last two in the data structure. I have to admit I am unsure what the "Internal job id" is.
Just to give you a glimpse of the information I can see in the PSDS I wrote this program of just three lines:
01 **free 02 /include cpysrc,rpg_psds 03 *inlr = *on ; |
Lines 2: I have used the /INCLUDE compiler directive to copy the contents of the PSDS into this program.
After compiling the program I started debug and put added a break point on the last line of the program. I see the following in the PSDS:
EVAL pgm PGM.PROC = 'TESTPGM ' PGM.STSCDE = 00000. PGM.PRVSTSCDE = 00000. PGM.SRCLINENBR = '00000000' PGM.ROUTINE = '*DETC ' PGM.PARMS = 000. PGM.EXCEPTION = ' ' PGM.EXCEPTIONTYPE = ' ' PGM.EXCEPTIONNBR = ' ' PGM.RESERVED1 = '0000' PGM.MSGWRKAREA = ' ' PGM.PGMLIB = 'MYLIB ' PGM.EXCEPTIONDATA = ....5...10...15...20...25...30...35...40...45...50...55...60 1 ' ' 61 ' ' PGM.RNX9001EXCEPTION = ' ' PGM.LASTFILE1 = ' ' PGM.UNUSED1 = ' ' PGM.DTEENTERED = '06122019' PGM.STRDTECENTURY = 20. PGM.LASTFILE2 = ' ' PGM.LASTFILESTS = ' ' PGM.JOBNAME = 'QPADEV0001' PGM.JOBUSER = 'SIMON ' PGM.JOBNBR = 526346. PGM.STRDTE = 061219. PGM.PGMDTE = 061219. PGM.PGMTIME = 032844. PGM.COMPILEDTE = '061219' PGM.COMPILETIME = '032826' PGM.COMPILERLEVEL = '0001' PGM.SRCFILE = 'DEVSRC ' PGM.SRCLIB = 'MYLIB ' PGM.SRCMBR = 'TESTPGM ' PGM.PROCPGM = 'TESTPGM ' PGM.PROCMOD = 'TESTPGM ' PGM.SRCLINENBRBIN = 48. PGM.LASTFILESTSBIN = 48. PGM.USER = 'SIMON ' PGM.EXTERRCODE = 0 PGM.INTOELEMENTS = 0 PGM.INTERNALJOBID = ' ? ? ?" sM´Þmǵ?' PGM.SYSNAME = 'DEV730 ' |
As I work in a multiple partition world being able to retrieve the System Name is going to help me to be able to condition code depending upon which partition the program is running on, without retrieving the network name using a CL command.
You can learn more about the Program Status Data Structure from the IBM website here.
This article was written for IBM i 7.4 and 7.3 TR6.
Simon. Love your work. I learn a lot. What is your opinion on using an externally described PF for the SDS definition? I have been using the same one for decades.
ReplyDeleteFor many years (possibly decades too), starting with RPG3, I used a file as you described.
DeleteMy opinion is if you prefer to use a file that is fine. If you want to make a source member and /INCLUDE it into your source that is fine too.
Use whichever one you feel comfortable with.
You can mix and match **FREE source members with /COPY. If the main source is not **FREE, you can copy a source member with *FREE, and if you main source is **FREE, you can copy a source member without **FREE.
ReplyDeleteThe "Internal Job ID" is used for the Work Management APIs.
ReplyDeleteThe APIs can locate a job from either the name/user/number or the internal job ID, but the RFE that requested this said that they sometimes have duplicate jobs on their system, so the name/user/number isn't sufficient to locate the job. The documentation for the Work Management APIs also says that the internal job ID can locate the job faster than name/user/number.
Here's the RFE: http://www.ibm.com/developerworks/rfe/execute?use_case=viewRfe&CR_ID=103671
When using the Internal Job ID, it's important to remember that this ID is reassigned to all currently registered jobs during an IPL. That is, the Internal ID does not persist following an IPL, and any given job will have a different Internal ID after the IPL. To avoid constant lookups of a job's Internal ID, I store the current IPL version of it in my own table of jobs I care about, and I also store the current IPL date along with the Internal ID. As long as the IPL date of the stored Internal ID is the same as the start date/time of the SCPF job, I know I can trust the Internal ID. Otherwise, I know I have to do a fresh fetch of the job's Internal ID (using either a QSYS2 Table inquiry or an IBM i API call). Careful planning of when to check the IPL date can reduce unnecessary function or API calls, thus taking best advantage of the speed of a reliable Internal ID search.
ReplyDelete