25
Wed, Dec
0 New Articles

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

APIs
Typography
  • 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                                      

                                                                       

 *********************************************************************

                                                                       

 /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…

  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.

BLOG COMMENTS POWERED BY DISQUS

LATEST COMMENTS

Support MC Press Online

$

Book Reviews

Resource Center

  • SB Profound WC 5536 Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application. You can find Part 1 here. In Part 2 of our free Node.js Webinar Series, Brian May teaches you the different tooling options available for writing code, debugging, and using Git for version control. Brian will briefly discuss the different tools available, and demonstrate his preferred setup for Node development on IBM i or any platform. Attend this webinar to learn:

  • SB Profound WP 5539More than ever, there is a demand for IT to deliver innovation. Your IBM i has been an essential part of your business operations for years. However, your organization may struggle to maintain the current system and implement new projects. The thousands of customers we've worked with and surveyed state that expectations regarding the digital footprint and vision of the company are not aligned with the current IT environment.

  • SB HelpSystems ROBOT Generic IBM announced the E1080 servers using the latest Power10 processor in September 2021. The most powerful processor from IBM to date, Power10 is designed to handle the demands of doing business in today’s high-tech atmosphere, including running cloud applications, supporting big data, and managing AI workloads. But what does Power10 mean for your data center? In this recorded webinar, IBMers Dan Sundt and Dylan Boday join IBM Power Champion Tom Huntington for a discussion on why Power10 technology is the right strategic investment if you run IBM i, AIX, or Linux. In this action-packed hour, Tom will share trends from the IBM i and AIX user communities while Dan and Dylan dive into the tech specs for key hardware, including:

  • Magic MarkTRY the one package that solves all your document design and printing challenges on all your platforms. Produce bar code labels, electronic forms, ad hoc reports, and RFID tags – without programming! MarkMagic is the only document design and print solution that combines report writing, WYSIWYG label and forms design, and conditional printing in one integrated product. Make sure your data survives when catastrophe hits. Request your trial now!  Request Now.

  • SB HelpSystems ROBOT GenericForms of ransomware has been around for over 30 years, and with more and more organizations suffering attacks each year, it continues to endure. What has made ransomware such a durable threat and what is the best way to combat it? In order to prevent ransomware, organizations must first understand how it works.

  • SB HelpSystems ROBOT GenericIT security is a top priority for businesses around the world, but most IBM i pros don’t know where to begin—and most cybersecurity experts don’t know IBM i. In this session, Robin Tatam explores the business impact of lax IBM i security, the top vulnerabilities putting IBM i at risk, and the steps you can take to protect your organization. If you’re looking to avoid unexpected downtime or corrupted data, you don’t want to miss this session.

  • SB HelpSystems ROBOT GenericCan you trust all of your users all of the time? A typical end user receives 16 malicious emails each month, but only 17 percent of these phishing campaigns are reported to IT. Once an attack is underway, most organizations won’t discover the breach until six months later. A staggering amount of damage can occur in that time. Despite these risks, 93 percent of organizations are leaving their IBM i systems vulnerable to cybercrime. In this on-demand webinar, IBM i security experts Robin Tatam and Sandi Moore will reveal:

  • FORTRA Disaster protection is vital to every business. Yet, it often consists of patched together procedures that are prone to error. From automatic backups to data encryption to media management, Robot automates the routine (yet often complex) tasks of iSeries backup and recovery, saving you time and money and making the process safer and more reliable. Automate your backups with the Robot Backup and Recovery Solution. Key features include:

  • FORTRAManaging messages on your IBM i can be more than a full-time job if you have to do it manually. Messages need a response and resources must be monitored—often over multiple systems and across platforms. How can you be sure you won’t miss important system events? Automate your message center with the Robot Message Management Solution. Key features include:

  • FORTRAThe thought of printing, distributing, and storing iSeries reports manually may reduce you to tears. Paper and labor costs associated with report generation can spiral out of control. Mountains of paper threaten to swamp your files. Robot automates report bursting, distribution, bundling, and archiving, and offers secure, selective online report viewing. Manage your reports with the Robot Report Management Solution. Key features include:

  • FORTRAFor over 30 years, Robot has been a leader in systems management for IBM i. With batch job creation and scheduling at its core, the Robot Job Scheduling Solution reduces the opportunity for human error and helps you maintain service levels, automating even the biggest, most complex runbooks. Manage your job schedule with the Robot Job Scheduling Solution. Key features include:

  • LANSA Business users want new applications now. Market and regulatory pressures require faster application updates and delivery into production. Your IBM i developers may be approaching retirement, and you see no sure way to fill their positions with experienced developers. In addition, you may be caught between maintaining your existing applications and the uncertainty of moving to something new.

  • LANSAWhen it comes to creating your business applications, there are hundreds of coding platforms and programming languages to choose from. These options range from very complex traditional programming languages to Low-Code platforms where sometimes no traditional coding experience is needed. Download our whitepaper, The Power of Writing Code in a Low-Code Solution, and:

  • LANSASupply Chain is becoming increasingly complex and unpredictable. From raw materials for manufacturing to food supply chains, the journey from source to production to delivery to consumers is marred with inefficiencies, manual processes, shortages, recalls, counterfeits, and scandals. In this webinar, we discuss how:

  • The MC Resource Centers bring you the widest selection of white papers, trial software, and on-demand webcasts for you to choose from. >> Review the list of White Papers, Trial Software or On-Demand Webcast at the MC Press Resource Center. >> Add the items to yru Cart and complet he checkout process and submit

  • Profound Logic Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application.

  • SB Profound WC 5536Join us for this hour-long webcast that will explore:

  • Fortra IT managers hoping to find new IBM i talent are discovering that the pool of experienced RPG programmers and operators or administrators with intimate knowledge of the operating system and the applications that run on it is small. This begs the question: How will you manage the platform that supports such a big part of your business? This guide offers strategies and software suggestions to help you plan IT staffing and resources and smooth the transition after your AS/400 talent retires. Read on to learn: