zPET - IBM Z and z/OS Platform Evaluation and Test

zPET - IBM Z and z/OS Platform Evaluation and Test

zPET - IBM Z and z/OS Platform Evaluation and Test

Experiences and tips from a team of system programmers and testers who run a Parallel Sysplex on which we perform the final verification of a z/OS release and System z hardware and System Storage before they become generally available to clients.

 View Only

Using IBM Data Set Commander for z/OS to get ISPF stats in Python

By Michael Cohoon posted Tue October 17, 2023 10:48 AM

  

z/OS Platform Evaluation and Test (zPET) runs customer-like workloads in a Parallel Sysplex environment to perform the final verification of new IBM Z hardware and software. For more information about zPET, please check out our community.

Overview

As usage of Python continues to grow in the mainframe environment, the language has become the go-to for new projects, automation, and scripts. While IBM Z Open Automation Utilities (ZOAU) provides many interfaces into z/OS and its resources, a recent zPET team project required – in part – gathering ISPF statistics for members in various datasets. This wasn’t functionality that (at the time of writing) ZOAU had available. After considering various methods of getting this information (such as LMMLIST in batch, DSFS, and more), we decided to leverage ZOAU and IBM Data Set Commander for z/OS.

Data Set Commander (DSC) is a software product that provides enhanced productivity in both ISPF and batch. It includes an interactive menu-driven front-end to ISPF as well as a batch utility with enhanced partitioned data set functions. The batch utility shipped with Data Set Commander is called IQIBUTIL and provides, among other things, a way to easily gather ISPF statistics for dataset members through batch. This capability, along with ZOAU’s ability to run MVS programs, provides a way to retrieve and work with ISPF statistics in Python.

In this blog, we discuss how we get dataset information with ZOAU’s datasets module, how to build the proper statements to run IQIBUTIL with ZOAU’s mvscmd module, and how we combined these sources of data into one usable Python object.

Please note: The snippets and examples showcased are provided as-is and have been slightly simplified for readability and consumability.
 

Retrieving Dataset Information

The datasets module provides numerous functions for working with z/OS datasets, including the ability to create, modify, and delete datasets and members. Since our project was one of discovery, we only needed to retrieve information for certain datasets, and this led us to the datasets.listing() function.

The datasets.listing() function retrieves various dataset information, such as the record format, block size, used space, and more. Here is a simple example using this function:

from zoautil_py import datasets

dsn_name = "ZPET.DSN001"
my_dataset = datasets.listing(dsn_name)[0] print(f"Object type:\n{type(my_dataset)}")
print()
print(f"Object contents:\n{my_dataset.__dict__}")

Output from the above example:

Object type:
<class 'zoautil_py.types.Dataset'>

Object contents:
{'name': 'ZPET.DSN001', 'recfm': 'FB', 'lrecl': '80', 'dsorg': 'PO', 'volume': VOLXYZ, 'block_size': '3120', 'last_referenced': '2023/10/02', 'used_space': '24516248', 'total_space': '27198720', 'vrbs': 0}


To iterate over a list of datasets, extracting volume details (though other information could also be accessed), and saving that in a dictionary for future use, we used the following:

from zoautil_py import datasets

# List of datasets needed to process
my_dsns = ["ZOS.CNTL.JCL",
           "ZPET.PLEX1.REXX",
           "USERA.LOCAL.JOBS",
           "ZPET.DSN001"
           ]

# Dictionary to store final results
member_info = dict()

# Iterate over each dataset
for dsn in my_dsns:
    listing_info = datasets.listing(dsn)[0]

    member_info[dsn] = {"volume": listing_info.volume}

To verify we have the volume information for each dataset, we can loop over the member_info dictionary, printing out the details. Here is how we printed out the dataset and volume information:

# Print out the contents of the dictionary so far
for dsn, dsn_info in member_info.items():
    print(f"Dataset: {dsn}, volume: {dsn_info.get('volume')}")

Example output:

Dataset: ZPET.DSN001, volume: VOLXYZ
Dataset: ZPET.PLEX1.JOBS, volume: PAKAAA
Dataset: USERA.LOCAL.JOBS, volume: LOCAL1
Dataset: ZOS.CNTL.JCL, volume: VOL01C

Running the IQIBUTIL program with JCL

The Data Set Commander’s IQIBUTIL program provides a batch utility for replacing IEBCOPY with enhanced partitioned data set manipulation functions. In addition to standard IEBCOPY functions, with IQIBUTIL you can list, copy, rename, delete, and restore members, with or without filters. In our case, we are interested in pulling the ISPF statistics for all members in a dataset without any filters. To do this, we needed to use the DIRLIST function of IQIBUTIL. Data Set Commander also provides an interface – the IQIBUTIL Batch Job Wizard - to help generate JCL.

Below is sample JCL for using IQIBUTIL to get the ISPF statistics for all members in the ZOS.CNTL.JCL dataset:

//IQIBUTIL JOB
//IQIBUTIL EXEC PGM=IQIBUTIL,
// PARM='GENS=NONE,NOSTAMP'
//STEPLIB  DD DISP=SHR,DSN=DSC.V9R1M0.SIQILOAD
//IQIBUDFL DD DISP=SHR,DSN=DSC.V9R1M0.SIQIPLIB
//SYSPRINT DD SYSOUT=*
//SYSUT1   DD DISP=SHR,DSN=ZOS.CNTL.JCL
//SYSUT2   DD SYSOUT=*
//SYSIN    DD *
  DIRLIST    OUTDD=SYSUT2,
             INDD=SYSUT1,
    LIST=YES
      SELECT MEMBER=*
//*

The output for the above JCL will be written to the job’s SYSUT2 dataset:

AMBLIST  001 01.02 2018/06/01 2023/01/26 16:12        7        7        0 USER01
COPYDS   001 01.02 2023/06/28 2023/06/28 14:22       15       12        0 USER01
DFDSSPRT 001 01.01 2018/02/06 2018/02/06 08:33       16       13        0 USER02
DFDSSPR1 001                                                                   
DLTVSAM  001 01.01 2018/03/05 2018/03/05 09:07        8        7        0 USER02
DSSPHYS  001 01.01 2019/04/17 2019/04/17 09:49       16       13        0 SYSPRG1
DSSPRINT 001 01.00 2018/02/01 2018/02/01 09:42       10       10        0 SYSPRG2
DUMPVTOC 001 01.00 2018/05/16 2018/05/16 16:50        9        9        0 ZOSUSER
ICETOOL  001 01.00 2023/08/16 2023/08/16 10:10        9        9        0 USER01
IEBPDSE  001                                                                   
LISTVTOC 001 01.01 2019/04/12 2019/04/12 09:58       10       12        0 USER01

Running the IQIBUTIL program with Python and ZOAU

While the above JCL is fine for many scenarios, if we needed to run IQIBUTIL as part of a larger Python program, we can leverage ZOAU and its mvscmd module. The mvscmd.execute() function allows us to run an MVS program, and we can use this to create a Python version of our above JCL. We will also need the import ZOAU’s DDStatement and DatasetDefinition.

The first step we need to do is create a temporary dataset with the contents we will provide to IQIBUTIL’s SYSIN. We found that this method is the best approach for handling in-line content with ZOAU. Here is an example of creating the temporary dataset:

from zoautil_py import datasets

temp_sysin_dsn_name = "LOCAL.IQIBUTIL.SYSIN.TEMP"

datasets.create(temp_sysin_dsn_name, type="SEQ", record_length=80,
                record_format="FB", block_size="27920", primary_space="5M")
datasets.write(temp_sysin_dsn_name, content="""
  DIRLIST    OUTDD=SYSUT2,
             INDD=SYSUT1,
    LIST=YES
      SELECT MEMBER=*
""")

With the SYSIN dataset created, we can prepare the dataset definition (DD) statements and run the IQIBUTIL program. We construct a list of these DD statements, and we leverage the DDStatement and DatasetDefinition classes provided by ZOAU to accomplish this. Here is the Python equivalent of our above JCL:

from zoautil_py import mvscmd
from zoautil_py.types import DDStatement, DatasetDefinition

dsn_name = "ZOS.CNTL.JCL"
temp_sysin_dsn_name = "LOCAL.IQIBUTIL.SYSIN.TEMP"

# Create the DD statements used by IQIBUTIL
dd_statements = []
dd_statements.append(DDStatement("STEPLIB",
                     DatasetDefinition('DSC.V9R1M0.SIQILOAD')))
dd_statements.append(DDStatement("IQIBUDFL",
                     DatasetDefinition('DSC.V9R1M0.SIQIPLIB')))
dd_statements.append(DDStatement("SYSPRINT", '*'))
dd_statements.append(DDStatement("SYSUT1", DatasetDefinition(dsn_name)))
dd_statements.append(DDStatement("SYSUT2", '*'))
dd_statements.append(DDStatement("SYSIN",
                     DatasetDefinition(temp_sysin_dsn_name)))

# Run the IQIBUTIL program
response = mvscmd.execute(pgm="IQIBUTIL",
                          pgm_args="GENS=NONE,NOSTAMP",
                          dds=dd_statements)

# Convert the response to a dictionary
return_dictionary = response.to_dict()

# Print the command’s output
print(return_dictionary.get("stdout_response"))

Output:

AMBLIST  001 01.02 2018/06/01 2023/01/26 16:12        7        7        0 USER01
COPYDS   001 01.02 2023/06/28 2023/06/28 14:22       15       12        0 USER01
DFDSSPRT 001 01.01 2018/02/06 2018/02/06 08:33       16       13        0 USER02
DFDSSPR1 001
DLTVSAM  001 01.01 2018/03/05 2018/03/05 09:07        8        7        0 USER02
DSSPHYS  001 01.01 2019/04/17 2019/04/17 09:49       16       13        0 SYSPRG1
DSSPRINT 001 01.00 2018/02/01 2018/02/01 09:42       10       10        0 SYSPGR2
DUMPVTOC 001 01.00 2018/05/16 2018/05/16 16:50        9        9        0 ZOSUSER
ICETOOL  001 01.00 2023/08/16 2023/08/16 10:10        9        9        0 USER01
IEBPDSE 
LISTVTOC 001 01.01 2019/04/12 2019/04/12 09:58       10       12        0 USER01
...

With this done, we can then delete our temporary SYSIN dataset:

from zoautil_py import datasets

temp_sysin_dsn_name = "LOCAL.IQIBUTIL.SYSIN.TEMP"

datasets.delete(temp_sysin_dsn_name)

Combining Information

The last step is to merge the original dataset information with the member statistics. We will combine this information into a single Python dictionary, utilizing the member_info variable we created earlier. We will also rewrite our IQIBUTIL Python code to loop over the list of datasets stored in the my_dsns variable. Finally, we will parse the output of IQIBUTIL to identify the keys and values for each member statistic. The entire Python script now looks like:

from zoautil_py import datasets, mvscmd
from zoautil_py.types import DDStatement, DatasetDefinition

# List of datasets needed to process
my_dsns = ["ZPET.DSN001",
           "ZPET.PLEX1.REXX ",
           "USERA.LOCAL.JOBS",
           "ZOS.CNTL.JCL"
           ]

# Name of the temporary data SYSIN dataset
temp_sysin_dsn_name = "LOCAL.IQIBUTIL.SYSIN.TEMP"

# Dictionary to store final results
member_info = dict()

# Create temporary SYSIN dataset
datasets.create(temp_sysin_dsn_name, type="SEQ", record_length=80, record_format="FB",
                block_size="27920", primary_space="5M")
datasets.write(temp_sysin_dsn_name, content="""
  DIRLIST    OUTDD=SYSUT2,
             INDD=SYSUT1,
    LIST=YES
      SELECT MEMBER=*
""")

# Iterate over each dataset
for dsn in my_dsns:
    listing_info = datasets.listing(dsn)[0]

    member_info[dsn] = {"volume": listing_info.volume,
                        "members": []}

    # Create the DD statements used by IQIBUTIL
    dd_statements = []
    dd_statements.append(DDStatement("STEPLIB",
                                    DatasetDefinition('DSC.V9R1M0.SIQILOAD')))
    dd_statements.append(DDStatement("IQIBUDFL",
                                    DatasetDefinition('DSC.V9R1M0.SIQIPLIB')))
    dd_statements.append(DDStatement("SYSPRINT", '*'))
    dd_statements.append(DDStatement("SYSUT1", DatasetDefinition(dsn)))
    dd_statements.append(DDStatement("SYSUT2", '*'))
    dd_statements.append(DDStatement("SYSIN",
                         DatasetDefinition(temp_sysin_dsn_name)))

    # Run the IQIBUTIL program
    response = mvscmd.execute(pgm="IQIBUTIL",
                              pgm_args="GENS=NONE,NOSTAMP",
                              dds=dd_statements)

    # Convert the response to a dictionary
    return_dictionary = response.to_dict()

    # Process each line of the program's output
    for stat_line in return_dictionary.get("stdout_response").split("\n"):
        stat_line = stat_line.strip()

        # IQIDSCPY indicates the end of the member statistics section
        if "IQIDSCPY" in stat_line:
            break

        # Parse the fields from each line
        stats_name = stat_line[:8].strip()
        stats_created = stat_line[19:29].strip()
        stats_updated = stat_line[30:40].strip()
        stats_userid = stat_line[74:84].strip()

        # Combine statistic information with the original member_info dictionary
        stats_dict = {}
        stats_dict["name"] = stats_name
        stats_dict["created"] = stats_created
        stats_dict["updated"] = stats_updated
        stats_dict["userid"] = stats_userid

        member_info[dsn]["members"].append(stats_dict)

# Delete temporary SYSIN dataset
datasets.delete(temp_sysin_dsn_name)

# Print out the final contents of the dictionary
for dsn, dsn_info in member_info.items():
    for member in dsn_info.get('members'):
        member_string = ", ".join([f"{k}: {v}" for  k, v in member.items()])
        print(f"Dataset: {dsn}, volume: {dsn_info.get('volume')}, {member_string}")

Here is the output from the above example:

Dataset: ZOS.CNTL.JCL, volume: VOL01C, name: AMBLIST, created: 2018/06/01, updated: 2023/01/26, userid: USER01
Dataset: ZOS.CNTL.JCL, volume: VOL01C, name: COPYDS, created: 2023/06/28, updated: 2023/06/28, userid: USER01
Dataset: ZOS.CNTL.JCL, volume: VOL01C, name: DFDSSPRT, created: 2018/02/06, updated: 2018/02/06, userid: USER02
Dataset: ZOS.CNTL.JCL, volume: VOL01C, name: DFDSSPRT1, created: , updated: , userid:
Dataset: ZOS.CNTL.JCL, volume: VOL01C, name: DLTVSAM, created: 2018/03/05, updated: 2018/03/05, userid: USER02
Dataset: ZOS.CNTL.JCL, volume: VOL01C, name: DSSPHYS, created: 2019/04/17, updated: 2019/04/17, userid: SYSPRG1
Dataset: ZOS.CNTL.JCL, volume: VOL01C, name: DSSPRINT, created: 2018/02/01, updated: 2018/02/01, userid: SYSPRG2
Dataset: ZOS.CNTL.JCL, volume: VOL01C, name: DUMPVTOC, created: 2018/05/16, updated: 2018/05/16, userid: ZOSUSER
Dataset: ZOS.CNTL.JCL, volume: VOL01C, name: ICETOOL, created: 2023/08/16, updated: 2023/08/16, userid: USER01
Dataset: ZOS.CNTL.JCL, volume: VOL01C, name: IEBPDSE, created: , updated: , userid:
Dataset: ZOS.CNTL.JCL, volume: VOL01C, name: LISTVTOC, created: 2019/04/12, updated: 2019/04/12, userid: USER01
Dataset: ZPET.DSN001, volume: VOLXYZ, name: ZWESLSTC, created: 2022/05/25, updated: 2022/05/31, userid: ZOSUSER
Dataset: ZPET.DSN001, volume: VOLXYZ, name: ZWESVSTC, created: 2020/06/01, updated: 2020/06/01, userid: USER01
Dataset: ZPET.PLEX1.REXX, volume: PAKAAA, name: CHECK, created: 2015/04/30, updated: 2015/05/11, userid: ZOSUSER
Dataset: ZPET.PLEX1.REXX, volume: PAKAAA, name: RUNCMD, created: 2007/04/26, updated: 2007/06/20, userid: ZOSUSER
Dataset: ZPET.PLEX1.REXX, volume: PAKAAA, name: SDSFREXX, created: 2015/04/29, updated: 2015/08/04, userid: SYSPRG1
Dataset: ZPET.PLEX1.REXX, volume: PAKAAA, name: ZFSTEST, created: 2007/09/17, updated: 2008/11/20, userid: ZOSUSER
Dataset: USERA.LOCAL.JOBS, volume: LOCAL1, name: BPXBATCH, created: 2022/10/07, updated: 2022/10/07, userid: USER01
Dataset: USERA.LOCAL.JOBS, volume: LOCAL1, name: JOBCNCL, created: 1996/02/01, updated: 2016/02/19, userid: SYSPRG1
Dataset: USERA.LOCAL.JOBS, volume: LOCAL1, name: STARTALL, created: 1996/02/01, updated: 2010/03/03, userid: SYSPRG1
Dataset: USERA.LOCAL.JOBS, volume: LOCAL1, name: STOPALL, created: 1996/02/01, updated: 2013/07/07, userid: SYSPRG1
Dataset: USERA.LOCAL.JOBS, volume: LOCAL1, name: TESTJB, created: 1996/02/01, updated: 2016/06/10, userid: SYSPRG2
Dataset: USERA.LOCAL.JOBS, volume: LOCAL1, name: VTOCINF, created: 2022/06/15, updated: 2023/08/23, userid: ZOSUSER
Dataset: USERA.LOCAL.JOBS, volume: LOCAL1, name: ZZZUPDN, created: 1996/02/01, updated: 2017/06/09, userid: SYSPRG2

This information and format served our purposes. Now, with the content in a dictionary format, it would be easy to convert to another object (like a DataFrame) or create a local class and object. This data can also be written to a file (such as a CSV, JSON, or others) to be consumed by another process or program.

Note: The above examples are straightforward and without error checking. It is advised to implement Python best practices, logging, and error checking as appropriate.

Conclusion

Overall, we found Python, ZOAU, and Data Set Commander were great tools to use in a task such as ours, and we were impressed how well things worked together. ZOAU’s mvscmd module was a critical piece in this process that allowed us to run Data Set Commander’s IQIBUTIL program easily. As we approach future projects that require processing datasets, we will be sure to consider both ZOAU and Data Set Commander.

References

IBM Z Open Automation Utilities

ZOAU samples repository

IBM Data Set Commander for z/OS

Related Articles

The Journey from JCL to Python: so easy even an old mainframer can do it. by Frank De Gilio

Top 8 Reasons for using Python instead of REXX for z/OS by Frank De Gilio

Authors

Michael Cohoon (mtcohoon@us.ibm.com)

Jean Mothersele (jean.mothersele@ibm.com)

1 comment
74 views

Permalink

Comments

Sun March 09, 2025 11:20 AM

Thanks for sharing @Michael Cohoon :-)