Let's continue our discussion about using the List Job API.
Last month, in the API Corner article "Counting Active Jobs by Subsystem and/or User," we saw how to use the List Job (QUSLJOB) API in order to count the number of active jobs on the system by various combinations of subsystem and initial user. We also saw that there were (at least) three areas of improvement that could be made to the program:
- Returning the count of active jobs within the text of CPF9898 was not very user-friendly if a CL program, running the CntActJob command, wanted to work with the number. The CL program would need to parse the first-level text of the message to locate the number, which can "move" based on the length of the SBS and USR values. Is there a way to eliminate this need for the calling CL program to parse the first-level text of the message in order to access the active job count?
- The user profile parameter of CntActJob refers to the initial user profile of the job. Is there a way to also obtain a count of active jobs that are currently running under a given user? That is, add a third parameter such as CURUSR (while continuing to support the SBS and USR filters of the CntActJob command).
- There's a limit of around 160,000 job entries that can be returned with a single call to the List Job API when using key 1906. If there are more than 160,000 active jobs on the system that meet our criteria, is there a way to access these additional job entries?
Hopefully you aren't too surprised to find that the answers to the above questions are yes, yes, and yes. In this article, we'll look at what changes can be made to the CntActJob command and program in order to provide these improvements.
The first thing we'll do is stop using the rather generic message CPF9898. CPF9898 is handy when all you want to do is send a text message because the message simply defines one field, with a size of up to 512 bytes, and ends the text with a period. With item 1 above, what we want to do is return well-formatted data so that another program can easily access the variable information that is provided by the CntActJob command (for instance, the number of active jobs for a given subsystem and user combination).
To accomplish this, we'll add a new message description with a message ID of ACT0001. The following two commands (Create Message File and Add Message Description) will create the message file PlayMsgs in your current library and add message description ACT0001 to PlayMsgs, respectively.
CrtMsgF MsgF(PlayMsgs)
AddMsgD MsgID(ACT0001) MsgF(*CurLib/PlayMsgs)
Msg('Subsystem &1 has &2 active jobs for user &3, current user &4: &5.')
Fmt((*Char 10) (*UBin 4) (*Char 10) (*Char 10) (*UBin 4))
The initial text associated with message ACT0001 (Subsystem xxx has yyy active jobs for user zzz) matches the text used last month with CPF9898. The additional text (current user aaa: bbb) reflects our planned enhancement to CntActJob per list item 2 above. This text is included here just so we don't have to later change the message description. The major improvement, though, is that the first-level text of ACT0001 uses replacement data variables for items such as the subsystem name rather than returning all of the text in one large text field (as done with CPF9898). By reviewing the Msg parameter and the Fmt parameter of AddMsgD, we can see that variable &1 is defined as a 10-byte character field representing the subsystem name, &2 is defined as a 4-byte unsigned integer representing the number of active jobs associated with an initial user, &3 is defined as a 10-byte character field representing the name of the initial user, &4 is defined as a 10-byte character field representing the name of the current user, and &5 is defined as a 4-byte unsigned integer representing the number of active jobs associated with the current user.
One way to have CntActJob send active job count information using our ACT0001 message, rather than CPF9898, is to define a data structure that maps to the replacement data variables of ACT0001. The following ACT0001 data structure and changed SndTotals() function accomplishes this (we'll get to the CurUsr_In and CurUsrJobCnt fields shortly).
d ACT0001 ds qualified
d Sbs_In 10a
d ActJobCnt 10u 0
d UsrPrf_In 10a
d CurUsr_In 10a
d CurUsrJobCnt 10u 0
p SndTotals b
d SndTotals pi
/free
ACT0001.Sbs_In = Sbs_In;
ACT0001.ActJobCnt = ActJobCnt;
ACT0001.UsrPrf_In = UsrPrf_In;
ACT0001.CurUsr_In = CurUsr_In;
ACT0001.CurUsrJobCnt = CurUsrJobCnt;
SndPgmMsg('ACT0001' :'PLAYMSGS *LIBL'
:ACT0001 :%size(ACT0001)
:'*COMP' :'*PGMBDY' :1 :MsgKey :QUSEC);
/end-free
p SndTotals e
Because part of the motivation for using ACT0001, rather than CPF9898, was to simplify life for developers wanting to access the job counts provided in the message, a sample CL program demonstrating how to directly access this variable data is provided at the end of this article.
The second thing we'll do is add support for returning the number of active jobs associated with a given current user. To start, we'll add a current user (CurUsr) parameter to the CntActJob command. This is done by adding the following parameter definition after the Usr parameter of last month's command.
Parm Kwd(CurUsr) Type(*Generic) Len(10) +
SpcVal((*ALL)) +
Min(0) Dft(*ALL) +
Prompt('Current user profile')
To create the CNTACTJOB command, use this command:
CrtCmd Cmd(CntActJob) Pgm(CntActJob)
In support of the CurUsr parameter of the CntActJob command, we also need to make some changes to the ChkActJob program. These changes are adding a new parameter on the prototype and interface specifications as in the following.
d CntActJob pr
d Sbs_In 10a const
d UsrPrf_In 10a const
d CurUsr_In 10a const
d CntActJob pi
d Sbs_In 10a const
d UsrPrf_In 10a const
d CurUsr_In 10a const
Now we add a few variables that will be used along the same lines as how Usr was supported last month:
d CurUsr_Ptr s *
d CurUsr s 10a based(CurUsr_Ptr)
d CurUsrJobCnt s 10u 0
d CurUsrOK s n
d LenCurUsr s 5u 0
Because we want to count only active jobs for a given current user if the job also meets the subsystem name requirements, we add a variable to indicate that the subsystem associated with the job qualifies:
d SbsDOK s n
And as you will see shortly, CntActJob will now be processing the job entries returned by the List Jobs API with two FOR loops—one for each returned job entry (controlled by variable X) and one for each key value entry returned for each job entry (controlled by variable Y). So a second loop control variable is needed.
d Y s 10i 0
Last month, we used key 1906 to access subsystem information on active jobs. This month, we will also use key 305, which will allow us to access current user information as well. Because we're adding an additional key value, CntActJob also needs to update NbrKeysToRtn so that the API can "see" this additional key.
KeyValues(1) = 1906; // Subsystem name
KeyValues(2) = 305; // Current user
NbrKeysToRtn = 2;
In the mainline of CntActJob, there are some minor changes related to checking for both Sbs_In and CurUsr_In being the special value *ALL and a determination of whether or not CurUsr_In represents a generic name. Likewise, there is additional work within the ProcessLst() procedure related to key 305 being processed. But all of this is very similar to the processing discussed last month when processing key 1906. These refinements can be found in the full program source provided later in this article and won't be repeated here for space reasons.
The last enhancement to CntActJob is related to accessing more job entries than can fit within one user space.
Many list APIs, when it's likely that the API can return more data than will fit within a single user space, support a parameter known as a Continuation handle. The size of this continuation handle parameter can vary across APIs (some might be 16 bytes in length, others 48 bytes, etc.), but the idea behind them is the same. A blank (or not passed) continuation handle value means the list should start at the beginning; a non-blank value being passed to the API means to resume the list from where it left off on a previous call to the API. For the List Jobs API, this continuation handle is the ninth parameter, a parameter that was not passed in last month's version of CntActJob. This month, CntActJob will test to see if only a partial list was returned on calls to List Jobs and, if that's the case, then call List Jobs again asking for the next set of job entries that will fit into the user space. This continual calling of List Jobs will end when the API indicates the list is complete.
In the generic header of list APIs, where we find information such as the number of list entries (LstJobsHdr.QUSNbrLE) and the offset to the first returned entry (LstJobsHdr.QUSOLD), there is also an Information status field (LstJobsHdr.QUSIS). This information status field can have one of three values:
- 'C'—The list is complete and accurate.
- 'I'—The list is incomplete and not accurate.
- 'P'—The list is partial but accurate.
For CntActJob, the two values of interest are 'C', indicating that the list of jobs to be returned is complete, and 'P', indicating that the list of jobs to be returned includes more jobs. The value of 'I', due to how CntActJob calls the List Jobs API with an error code parameter indicating that errors should be returned as escape messages (QUSEC with QUSBPrv set to 0), will not be encountered (though it could be if another program and/or job was creating the list and CntActJob was accessing the generated list at a later time) as CntActJob is not monitoring for escape messages.
When an information status value of 'P' is returned by the API, List Jobs also returns a continuation handle value enabling CntActJob to continue the list. This value can be found in the Header section of the user space with the generic header providing an offset to the header section (LstJobsHdr.QUSOHS). The definition of this API-specific header section is provided in the QSYSINC include for the API and, for ILE RPG, is the data structure QUSLH with subfield QUSCH03 providing the continuation handle value to resume the list.
With that introduction, here are additional definitions and mainline logic changes needed to access a complete list of jobs:
d APIHdr_Ptr s *
d APIHdr ds likeds(QUSLH)
dow 1 = 1;
ProcessLst();
select;
when LstJobsHdr.QUSIS = 'C';
// All jobs have been processed
leave;
when LstJobsHdr.QUSIS = 'P';
// More jobs to process
APIHdr_Ptr = LstJobsHdr_Ptr + LstJobsHdr.QUSOHS;
LstJobs(LstJobsSpc :'JOBL0200'
:('*ALL ' + UsrPrf_In + '*ALL ')
:'*ACTIVE' :QUSEC :'*'
:NbrKeysToRtn :KeyValues :APIHdr.QUSCH03);
endsl;
enddo;
Rather than just calling ProcessLst() once, as was done last month, CntActJob now calls ProcessLst() within a DOW that runs until we explicitly leave the DOW. Within the DOW, ProcessLst() is called and after the current list entries have been processed, a check is made to determine if the entire list has been processed (LstJobsHdr.QUSIS is 'C') or if additional job entries exist (LstJobsHdr.QUSIS is 'P'). If the list is complete, the DOW is left. If the list is not complete, CntActJob accesses the API header section (APIHdr) using LstJobsHdr.QUSOHS, re-calls the List Jobs API using the continuation handle value found at APIHdr.QUSCH03, and reruns the DOW. That's it!
Here is the complete source for CntActJob, consolidating all the various changes we've discussed.
h DftActGrp(*no)
d CntActJob pr
d Sbs_In 10a const
d UsrPrf_In 10a const
d CurUsr_In 10a const
d CntActJob pi
d Sbs_In 10a const
d UsrPrf_In 10a const
d CurUsr_In 10a const
************************************************************
d CrtUsrSpc pr extpgm('QUSCRTUS')
d QualUsrSpcN 20a const
d XAttr 10a const
d IntSize 10i 0 const
d IntValue 1a const
d PubAut 10a const
d TxtDesc 50a const
d ReplaceOpt 10a const options(*nopass)
d ErrCde likeds(QUSEC) options(*nopass)
d Domain 10a const options(*nopass)
d TfrSize 10i 0 const options(*nopass)
d OptSpcAlgn 1a const options(*nopass)
d LstJobs pr extpgm('QUSLJOB')
d QualUsrSpcN 20a const
d Format 8a const
d QualJobName 26a const
d Status 10a const
d ErrCde likeds(QUSEC) options(*nopass)
d JobType 1a const options(*nopass)
d NbrFldsToRtn 10i 0 const options(*nopass)
d FldsToRtn const options(*nopass)
d like(KeyValues)
d dim(%elem(KeyValues))
d ContinueHdl 48a const options(*nopass)
d ProcessLst pr
d RtvUsrSpcPtr pr extpgm('QUSPTRUS')
d QualUsrSpcN 20a const
d UsrSpcPtr *
d ErrCde likeds(QUSEC) options(*nopass)
d SndPgmMsg pr extpgm('QMHSNDPM')
d MsgID 7a const
d QualMsgF 20a const
d MsgDta 256a const options(*varsize)
d LenMsgDta 10i 0 const
d MsgType 10a const
d CSE 10a const
d CSECtr 10i 0 const
d MsgKey 4a
d ErrCde likeds(QUSEC)
d LenCSE 10i 0 const options(*nopass)
d CSEQual 20a const options(*nopass)
d DspMsgWait 10i 0 const options(*nopass)
d CSEType 10a const options(*nopass)
d CCSID 10i 0 const options(*nopass)
d SndTotals pr
************************************************************
d LstJobsHdr_Ptr s *
d LstJObsHdr ds likeds(QUSH0100)
d based(LstJobsHdr_Ptr)
d JobHdr_Ptr s *
d JobHdr ds likeds(QUSL020001)
d based(JobHdr_Ptr)
d JobFlds_Ptr s *
d JobFlds ds likeds(QUSLKF)
d based(JobFlds_Ptr)
d QualSbsD_Ptr s *
d QualSbsD ds qualified
d based(QualSbsD_Ptr)
d SbsDName 10a
d SbsDLib 10a
d APIHdr_Ptr s *
d APIHdr ds likeds(QUSLH)
d ACT0001 ds qualified
d Sbs_In 10a
d ActJobCnt 10u 0
d UsrPrf_In 10a
d CurUsr_In 10a
d CurUsrJobCnt 10u 0
d ErrCde ds qualified
d Hdr likeds(QUSEC)
d MsgDta 256
************************************************************
d ActJobCnt s 10u 0
d CurUsr_Ptr s *
d CurUsr s 10a based(CurUsr_Ptr)
d CurUsrJobCnt s 10u 0
d CurUsrOK s n
d KeyValues s 10i 0 dim(MaxKeys)
d LenCurUsr s 5u 0
d LenSbsD s 5u 0
d LstJobsSpc s 20a inz('CNTACTJOB QTEMP')
d MsgDta s 512a
d MsgKey s 4a
d NbrKeysToRtn s 10i 0
d SbsDOK s n
d X s 10i 0
d Y s 10i 0
d MaxKeys c const(10)
************************************************************
/copy qsysinc/qrpglesrc,qusec
/copy qsysinc/qrpglesrc,qusgen
/copy qsysinc/qrpglesrc,qusljob
************************************************************
/free
KeyValues(1) = 1906; // Subsystem name
KeyValues(2) = 305; // Current user
NbrKeysToRtn = 2;
LstJobs(LstJobsSpc :'JOBL0200'
:('*ALL ' + UsrPrf_In + '*ALL ')
:'*ACTIVE' :QUSEC :'*'
:NbrKeysToRtn :KeyValues);
if ((Sbs_In = '*ALL') and
(CurUsr_In = '*ALL'));
// Check for all filters being set to *ALL
ActJobCnt = LstJobsHdr.QUSNbrLE;
CurUsrJobCnt = LstJobsHdr.QUSNbrLE;
else;
// Determine if Sbs_In is a generic
if %subst(Sbs_In :(%len(%trimr(Sbs_In))) :1) = '*';
LenSbsD = (%len(%trimr(Sbs_In))) - 1;
else;
LenSbsD = %size(Sbs_In);
endif;
// Determine if CurUsr_In is a generic
if %subst(CurUsr_In :(%len(%trimr(CurUsr_In))) :1) = '*';
LenCurUsr = (%len(%trimr(CurUsr_In))) - 1;
else;
LenCurUsr = %size(CurUsr_In);
endif;
dow 1 = 1;
ProcessLst();
select;
when LstJobsHdr.QUSIS = 'C';
// All jobs have been processed
leave;
when LstJobsHdr.QUSIS = 'P';
// More jobs to process
APIHdr_Ptr = LstJobsHdr_Ptr + LstJobsHdr.QUSOHS;
LstJobs(LstJobsSpc :'JOBL0200'
:('*ALL ' + UsrPrf_In + '*ALL ')
:'*ACTIVE' :QUSEC :'*'
:NbrKeysToRtn :KeyValues :APIHdr.QUSCH03);
endsl;
enddo;
endif;
SndTotals();
*inlr = *on;
return;
// ********************************************************
begsr *inzsr;
// Set appropriate API Errcde error handling values
QUSBPrv = 0;
ErrCde.Hdr.QUSBPrv = %size(ErrCde);
// Prepare to call QUSLJOB to get a list of all active
// jobs on the system
RtvUsrSpcPtr(LstJobsSpc :LstJobsHdr_Ptr :ErrCde);
select;
when ErrCde.Hdr.QUSBAvl = 0;
// All is OK
when ErrCde.Hdr.QUSEI = 'CPF9801';
// UsrSpc not found, so create it
CrtUsrSpc(LstJobsSpc :'ActJob_Lst' :4096
:x'00' :'*ALL' :'List of active jobs'
:'*YES' :QUSEC :'*DEFAULT' :0 :'1');
// Get accessibility to user space
RtvUsrSpcPtr(LstJobsSpc :LstJobsHdr_Ptr :QUSEC);
other;
// Something seriously wrong. Send Escape
MsgDta = 'Failure accessing UsrSpc' +
%trimr(LstJobsSpc) + ': ' +
ErrCde.Hdr.QUSEI;
SndPgmMsg('CPF9898' :'QCPFMSG *LIBL'
:MsgDta :%len(%trimr(MsgDta))
:'*ESCAPE' :'*PGMBDY' :1
:MsgKey :QUSEC);
endsl;
endsr;
/end-free
************************************************************
p ProcessLst b
d ProcessLst pi
/free
for X = 1 to LstJobsHdr.QUSNbrLE;
// Loop through potential Jobs, tracking what meets
// filter requirements
if X = 1;
JobHdr_Ptr = LstJobsHdr_Ptr + LstJobsHdr.QUSOLD;
else;
JobHdr_Ptr += LstJobsHdr.QUSSEE;
endif;
if JobHdr.QUSJIS = *blanks;
SbsDOK = *off;
CurUsrOK = *off;
for Y = 1 to JobHdr.QUSNbrFR;
// Access all additional info
if Y = 1;
JobFlds_Ptr = JobHdr_Ptr + %size(JobHdr);
else;
JobFlds_Ptr += JobFlds.QUSLFIR;
endif;
select;
when JobFlds.QUSKF = 1906;
QualSbsD_Ptr = JobFlds_Ptr + %size(JobFlds);
if ((Sbs_In = '*ALL') or
(%subst(QualSbsD.SbsDName :1 :LenSbsD) =
%subst(Sbs_In :1 :LenSbsD)));
SbsDOK = *on;
endif;
when JobFlds.QUSKF = 305;
CurUsr_Ptr = JobFlds_Ptr + %size(JobFlds);
if ((CurUsr_In = '*ALL') or
(%subst(CurUsr :1 :LenCurUsr) =
%subst(CurUsr_In :1 :LenCurUsr)));
CurUsrOK = *on;
endif;
endsl;
endfor;
if SbsDOK;
ActJobCnt += 1;
if ((SbsDOK) and (CurUsrOK));
CurUsrJobCnt += 1;
endif;
endif;
endif;
endfor;
/end-free
p ProcessLst e
************************************************************
p SndTotals b
d SndTotals pi
/free
ACT0001.Sbs_In = Sbs_In;
ACT0001.ActJobCnt = ActJobCnt;
ACT0001.UsrPrf_In = UsrPrf_In;
ACT0001.CurUsr_In = CurUsr_In;
ACT0001.CurUsrJobCnt = CurUsrJobCnt;
SndPgmMsg('ACT0001' :'PLAYMSGS *LIBL'
:ACT0001 :%size(ACT0001)
:'*COMP' :'*PGMBDY' :1 :MsgKey :QUSEC);
/end-free
p SndTotals e
Assuming that you have stored the above program source in source file QRPGLESRC and that the library containing QRPGLESRC is in your current library list, then you can create the CntActJob program with the following command:
CrtBndRPG Pgm(CntActJob)
To test the program and determine the number of active jobs currently running under the user profile BVINING across all subsystems and initial users, you can enter the following command:
CntActJob Sbs(*All) Usr(*All) CurUsr(BVining)
You may then see a message such as 'Subsystem *ALL has 3864 active jobs for user *ALL, current user BVINING: 4'.
Before I finish, just one note related to an earlier statement. You may have noticed that I previously mentioned "Many list APIs…support a parameter known as a Continuation handle" rather than "All" list APIs. Some list APIs can return more data than will fit in a single user space and do not provide a continuation handle parameter. The List Objects (QUSLOBJ) API is one example. In cases like this, an alternative API using an Open list approach is available, with the List Objects alternative being Open List of Objects (QGYOLOBJ). Two previous "API Corner" articles covering QGYOLOBJ and working with open lists include "Finding All *SRVPGMs on the System" and "Take Advantage of Open List APIs."
As mentioned earlier, one of the enhancements made to CntActJob was to make it easier for a CL program to run the CntActJob command and then work with the variable data returned in the message. The following program demonstrates how to run CntActJob and directly access the replacement data values.
Pgm
Dcl Var(&ACT0001) Type(*Char) Len(38)
Dcl Var(&SbsName) Type(*Char) Stg(*Defined) +
Len(10) DefVar(&ACT0001 01)
Dcl Var(&InlUsrCnt) Type(*UInt) Stg(*Defined) +
Len(4) DefVar(&ACT0001 11)
Dcl Var(&InlUsrName) Type(*Char) Stg(*Defined) +
Len(10) DefVar(&ACT0001 15)
Dcl Var(&CurUsrName) Type(*Char) Stg(*Defined) +
Len(10) DefVar(&ACT0001 25)
Dcl Var(&CurUsrCnt) Type(*UInt) Stg(*Defined) +
Len(4) DefVar(&ACT0001 35)
Dcl Var(&MsgID) Type(*Char) Len(7)
Dcl Var(&MsgDtaLen) Type(*Dec) Len(5 0) Value(38)
CntActJob Sbs(*All) CurUsr(BVINING)
RcvMsg Rmv(*No) MsgDta(&ACT0001) +
MsgDtaLen(&MsgDtaLen) MsgID(&MsgID)
If Cond(&MsgID = 'ACT0001') Then(Do)
/* Whatever is appropriate */
EndDo
EndPgm
In the same manner that the CntActJob RPG program defines the data structure ACT0001 to send message ACT0001, here the CL program defines the data structure &ACT0001 to map the replacement data variables of the message being received. After running the CntActJob command, the CL program uses the Receive Message command, using the MsgDta parameter to specify where the message replacement data should be returned (&ACT0001), the MsgDtaLen parameter to specify how large the target variable &ACT0001 is (38 bytes), and the MsgID parameter to specify where the message ID of the message being received should be returned (&MsgID). If the received message is ACT0001, then the various subfield values of &ACT0001 (&SbsName through &CurUsrCnt) can be directly accessed.
Using the previous test case of CntActJob Sbs(*All) Usr(*All) CurUsr(BVining), the value of &SbsName will be *ALL, &InlUsrCnt will be 3864, &InlUsrName will be *ALL, &CurUsrName will be BVINING, and &CurUsrCnt will be 4.
As usual, if you have any API questions, send them to me at
LATEST COMMENTS
MC Press Online