The API Corner: Counting Active Jobs by Subsystem and/or User

  • Smaller Small Medium Big Bigger
  • Default Helvetica Segoe Georgia Times


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                                      






  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;                        



     // Determine if Sbs_In is a generic                      


     if %subst(Sbs_In :(%len(%trimr(Sbs_In))) :1) = '*';     

        LenSbsD = (%len(%trimr(Sbs_In))) - 1;                


        LenSbsD = %size(Sbs_In);                             








  *inlr = *on;                                                        



  // *****************************************************************


  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);                 



       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);   



            // Something seriously wrong. Send Escape          


            MsgDta = 'Failure accessing UsrSpc' +               

                     %trimr(LstJobsSpc) + ': ' +               


            SndPgmMsg('CPF9898' :'QCPFMSG   *LIBL'             

                      :MsgDta :%size(MsgDta)                    

                      :'*ESCAPE' :'*PGMBDY' :1                        

                      :MsgKey :QUSEC);                                









p ProcessLst      b                                                    

d ProcessLst      pi                                                  




  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;             


         JobHdr_Ptr += LstJobsHdr.QUSSEE;                  



      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;                             








p ProcessLst      e                                                  




p SndTotals       b                                                   

d SndTotals       pi                                                 




  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);                  



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…

  1. 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.
  2. 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.
  3. 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.
  4. 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 youfor 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:

  1. 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.
  2. 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:

  1. 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.
  2. Run the ProcessLst procedure.
  3. 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):
    1. 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.
    2. 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)).
    3. 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.
    4. The FOR loop is then re-run in order to access the next job entry.
  4. 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:

  1. 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?
  2. 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).
  3. 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 This email address is being protected from spambots. You need JavaScript enabled to view it.. I'll see what I can do about answering your burning questions in future columns.