You can do it by using the List Job API.
I recently received a note from Brian M. that included this:
"…can a program be written to determine the number of active jobs attached to a specified subsystem?...I have been asked to figure out the number of active jobs under a specified subsystem like QINTER or QSERVER and be able to either say ALL users or a specified user like XYZ.… I know the command WRKSBSJOB can let me specify a subsystem and a user but it will not give a total so I have to manually count them…"
Such a program can certainly be written, using a variety of API and non-API approaches. As this is the API Corner, we'll use an API, specifically the List Job API (QUSLJOB), to gather the necessary information and the Send Program Message API (QMHSNDPM) to return the active job count as a message. An alternative approach, but still using an API, would be to call the Open List of Jobs API (QGYOLJOB).
Before getting to the program, though, let's first create a command interface to the program we'll be implementing. The command name is Count Active Jobs (CNTACTJOB), and below is the source for the command.
Cmd Prompt('Count Active Jobs')
Parm Kwd(Sbs) Type(*Generic) Len(10) +
SpcVal((*ALL)) Min(1) +
Prompt('Subsystem name')
Parm Kwd(Usr) Type(*Generic) Len(10) +
SpcVal((*ALL)) +
Min(0) Dft(*ALL) +
Prompt('Initial user profile')
The command has two parameters: SBS and USR. SBS is a required parameter and specifies the subsystem(s) to be included in the count. You can identify a specific subsystem name, a generic subsystem name, or the special value *ALL. USR is the initial user starting the job (as opposed to the current user the job may have swapped to) and can be specified as a specific user profile name, a generic user profile name, or the special value *ALL. The default USR value is *ALL.
To create the CNTACTJOB command use this command:
CrtCmd Cmd(CntActJob) Pgm(CntActJob)
We're not providing any help text for the CNTACTJOB command as it's not exactly API-related, but if you are interested in how command help text can be added, you may want to look at some earlier columns from the CL Corner: Need Some Help with That Command?, Need Some Help with That Command? Part II, and Getting Your Commands Out to the World.
Below is the source for the command processing program CntActJob.
h DftActGrp(*no)
d CntActJob pr
d Sbs_In 10a const
d UsrPrf_In 10a const
d CntActJob pi
d Sbs_In 10a const
d UsrPrf_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 ErrCde ds qualified
d Hdr likeds(QUSEC)
d MsgDta 256
*********************************************************************
d ActJobCnt s 10u 0
d KeyValues s 10i 0 dim(MaxKeys)
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 X 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
NbrKeysToRtn = 1;
LstJobs(LstJobsSpc :'JOBL0200'
:('*ALL ' + UsrPrf_In + '*ALL ')
:'*ACTIVE' :QUSEC :'*'
:NbrKeysToRtn :KeyValues);
if Sbs_In = '*ALL';
// Unlikely, but check for *ALL subsystems
// to avoid unnecessary processing
ActJobCnt = 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;
ProcessLst();
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 :%size(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, count if running in Sbs_In
if X = 1;
JobHdr_Ptr = LstJobsHdr_Ptr + LstJobsHdr.QUSOLD;
else;
JobHdr_Ptr += LstJobsHdr.QUSSEE;
endif;
if ((JobHdr.QUSJIS = *blanks) and
(JobHdr.QUSNbrFR > 0));
// Access the additional info (Subsystem)
JobFlds_Ptr = JobHdr_Ptr + %size(JobHdr);
if JobFlds.QUSKF = 1906;
QualSbsD_Ptr = JobFlds_Ptr + %size(JobFlds);
if %subst(QualSbsD.SbsDName :1 :LenSbsD) =
%subst(Sbs_In :1 :LenSbsD);
ActJobCnt += 1;
endif;
endif;
endif;
endfor;
/end-free
p ProcessLst e
*********************************************************************
p SndTotals b
d SndTotals pi
/free
MsgDta = 'Subsystem ' + %trimr(Sbs_In) +
' has ' + %char(ActJobCnt) +
' active jobs for user ' + %trimr(UsrPrf_In);
SndPgmMsg('CPF9898' :'QCPFMSG *LIBL'
:MsgDta :%size(MsgDta)
:'*COMP' :'*PGMBDY' :1 :MsgKey :QUSEC);
/end-free
The CntActJob program defines two input parameters:
- Sbs_In identifies the subsystem(s) of interest.
- UsrPrf_In identifies the user profile(s) of interest in terms of starting the job.
Upon entry to CntActJob, the *inzsr subroutine is run to set the environment for the main processing of the program. In the *inzsr, we…
- Initialize the Bytes provided field of the QUSEC error code structure to 0. The QUSEC error code structure is used on API calls when the program has no error recovery in place (it must be a really bizarre error) so an exception will be sent to the caller.
- Initialize the Bytes provided field of the ErrCde error code structure to 272, the size of the ErrCde structure. The ErrCde error code structure is used on API calls when some form of error recovery is in place.
- Attempt to access the user space QTEMP/CNTACTJOB using the ErrCde error code structure. If this is the initial call of CntActJob in the job, the user space will not exist, so it's created. In either case, variable LstJobsHdr_Ptr is set to the address of the *USRSPC.
- Return control to the mainline of the program.
In the mainline processing, CntActJob calls the List Job (QUSLJOB) API, asking that a list of all active jobs on the system that have a job user name of UsrPrf_In be returned in the user space QTEMP/CNTACTJOB using format JOBL0200. Format JOBL0200 is one of two formats supported by the QUSLJOB API.
Format JOBL0100, previously introduced in the API Corner article Determining Whether Jobs Are Currently Active of December 2013, can be used if we're interested only in basic job information, such as the full job name, job type, and job subtype.
Format JOBL0200 provides all of the information found in JOBL0100 and also has the ability to have additional, user-specified information returned. This additional information is identified by the optional Number of fields to return and Key of fields to return parameters of the QUSLJOB API. The Key of fields parameter is an array of 4-byte binary/integer values identifying the additional fields we want returned. In the case of CntActJob, the Number of fields to return is set to 1 (NbrKeysToRtn), and the first element of the KeyValues array is set to the value 1906 immediately prior to calling the List Job API. Reviewing the API documentation at label Valid Keys, you can see that key 1906 is defined as identifying the qualified subsystem description name (which we may need to examine). In addition, you'll notice that there's a whole lot of other information that can be returned to you—for instance, Active job status, which was used in article Keep Those Batch Jobs Running (Or How to Enjoy Your Off Time) back in September of 2008.
Two additional points about the call to QUSLJOB:
- The Error code parameter is set to the data structure QUSEC. If there are any errors detected within the API, then using this error code structure will cause the API to send an escape message. QUSEC is being used as I don't have any "handy" error recovery; something is just plain wrong, and we need to make a change to CntActJob.
- The user name portion of the Qualified job name parameter is set to UsrPrf_In. The API, just like our CntActJob command, directly supports a specific user profile name, a generic name, and the special value *ALL, so we don't have to do anything special here. The API actually supports another special value, *CURRENT, but our definition of the CntActJob command would block that value from coming in.
When the QUSLJOB API returns control to our program, we first make a quick check to see if the requested active job count is to include all subsystems (parmeter Sbs_In is '*ALL'). If so, then there is no need to filter the count based on the subsystem name; we can simply return the number of jobs that were returned by the API. This value can be found in the generic header of the *USRSPC as the number of list entries returned (LstJobsHdr.QUSNbrLE). The procedure SndTotals() is later called to actually format and send the message providing the number of active jobs.
When there is a need to filter the active job count based on the Sbs_In value (Sbs_In is not set to '*ALL'), then CntActJob will perform these steps:
- Determine the length of the jobs subsystem name that needs to match with the user specified Sbs_In parameter value. This is done by examining the last non-blank character of Sbs_In. If an asterisk is found, indicating a generic subsystem name, then the length to compare (LenSbsD) is set to the byte location of the asterisk less 1. If an asterisk is not found, then the length to compare is set to the full length of the subsystem name, 10 bytes.
- Run the ProcessLst procedure.
- ProcessLst enters into a FOR loop to examine each of the returned job entries (LstJobsHdr.QUSNbrLE). To access the first entry (X = 1), CntActJob takes the address of the *USRSPC (LstJobsHdr_Ptr) and adds to it the offset to the returned list data (LstJobsHdr.QUSOLD). Subsequent entries are accessed by taking the address of the current entry (JobHdr_Ptr) and adding the size of each job entry (LstJobsHdr.QUSSEE):
- For each entry, CntActJob first determines if the job information is available. This is done by checking the Job Information Status field (JobHdr.QUSJIS) for a blank value. A non-blank value indicates that the information was not available. For instance, a value of 'A' means that the user of the API was not authorized to the job. A second check, and one that is probably not necessary, is that the data associated with at least one of the specified keys (1906 in our case) was returned.
- If both of these checks are good (JobHdr.QUSJIS = *blanks and JobHdr.QUSnbrFR > 0), then CntActJob accesses the job information by taking the address of the job entry (JobHdr_Ptr) and adding to it the size of the job header information (%size(JobHdr)).
- A check is made that the additional information is indeed for key 1906 and if so accesses the subsystem description name by taking the address of the additional information structure (JobFlds_Ptr) and adding to it the size of the structure (%size(JobFlds)). This provides us with the subsystem name (QualSbsD.SbsDName). If the value of QualSbsD.SbsDName, for the length found in LenSbsD, is equal to the value of Sbs_In, again for the length found in LenSbsD, then ActJobCnt is incremented by 1. ActJobCnt reflects the number of active jobs found that meet the criteria of Sbs_In and UsrPrf_In.
- The FOR loop is then re-run in order to access the next job entry.
- After the FOR loop has completed, all returned jobs entries have been examined and ProcessLst() returns to the mainline of the program.
Having now determined the number of active jobs (either due to Sbs_In being set to the special value '*ALL' or having run the ProcessLst procedure, CntActJob now uses the SndTotals() procedure to format and send the message providing the number of active jobs.
The SndTotals() procedure is quite straightforward. It simply formats a text message containing the values of Sbs_In, UsrPrf_In, and ActJobCnt and then sends the message by calling the Send Program Message API. The actual message description used is CPF9898, and the message is sent as a completion message to the caller of the CntActJob program.
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 started by BVINING that are running in any subsystem with the first six bytes being QINTER, you can enter the following command:
CntActJob Sbs(QInter*) Usr(BVining)
You may then see a message such as "Subsystem QINTER* has 2 active jobs for user BVINING" or, more likely, "Subsystem QINTER* has 0 active jobs for user BVINING."
Now I will point out that, as provided, the CntActJob program is making (at least) one assumption that may or may not hold true for your environment. The number of job entries that can be returned by one call to the List Job API is limited to the size of a *USRSPC (which is just a tad under 16MB). With each job entry requested by CntActJob having a size of 100 bytes (determined by looking at the returned value Size of each entry (LstJobsHdr.QUSSEE)), the API can only return in the neighborhood of 160,000 job entries per call. If your system has an active job count this high, then please make sure to read next month's article as there is a way to handle this situation.
Before closing, here are three exercises if you're interested in such things:
- Returning the count of active jobs within the text of CPF9898 meets the requirement of providing a count, but it's not very user-friendly if a CL program, running the CntActJob command, wants 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. Can you come up with 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?
- It was mentioned earlier that the user profile parameter of CntActJob refers to the initial user profile of the job. Can you see how 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).
- As noted previously, there is 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. Can you determine what is needed in CntActJob to access the "next" 160,000 or so job entries?
My code changes to CntActJob (which most assuredly will not be the only correct answers) to these questions will be in next month's API Corner.
As usual, if you have any API questions, send them to me at
LATEST COMMENTS
MC Press Online