Hi everyone, so this is my first post ever however I've been following this forum for so many years now and I always enjoy reading all the content.
So, HEY!
Basically, as I mentioned in the subject, I'm looking for a way to add a step counter inside my rexx stack, however, I reached a point where I'm not sure where to go from here.. basically, my rexx will read data from 2 datasets (source / target) and for each 1 row that is read on these datasets, 4 job steps are created, so first I do write a job card to the member and then once each dataset is read those from source/target the new 4 steps are created, so every time the stack resets and goes over again until it reaches the end of the file..
Is there a way to put a step counter for each step that it is generated, however, do not reset this counter ?? I was investigating MAKEBUFF command.. perhaps this is what I am looking for ??
Follow the code;
As you may see, each step has a STEP'S' on it, I tried putting a counter with S = 0 ; S = S + 1 but it didnt work due the stack I guess..
Appreciate the support if possible.
Thanks !
Code:
/*-------------------------------------------------------------------*/
"newstack" /* */
Outn=0 /* Clear the variable OutNumber */
JobN=0 /* Clear the variable: JobNumber */
/*--------------------------------*/
do dsn=1 to dsls.0 /* init DataSetList for Source */
do dsn=1 to dslt.0 /* init DataSetList for Target */
SRCName = WORD(DSLs.dsn,1) /* Variable to identify source dataset */
TGTName = WORD(DSLt.dsn,1) /* Variable to identify target dataset */
/* QUEUE Below is used to generate steps whenever a dataset is read from
Source and Target members contained in JobLib */
Variables are not affected by the stack. Both DOs are using the same control variable, surely that is not intended? And I can't see you setting the S variable anywhere. You need to initialise S before the first DO and then update it inside the second DO.
Thanks Willy for the tip, so I managed to add the step counter.
I know the code doesn't look perfect, I still need to go thru and review it and will spend some time always to improve and remove unnecessary stuff and adding more stuff, I always wanted to do coding so I am enjoying it each successful step (and learning with the bad choices lol)
So, following your tip, I managed to add the step counter.. but I got hit by another issue.. for each listed dataset in my input file, 4 steps are generated.. so the step counter is adding a value on a 4 step scale .. would there be a way to add one step counter for each one of the 4 steps ? like .. something continuous STEP1 , STEP2 , STEP3 , STEP4 , STEP 5 , STEP6.. perhaps create one variable for each of the 4 QUEUED steps ?
Here's the code now;
Code:
/*-------------------------------------------------------------------*/
"newstack" /* */
Outn=0 /* Clear the variable OutNumber */
JobN=0 /* Clear the variable: JobNumber */
S=0 /* Variable for Step Counter */
/*--------------------------------*/
do dsn=1 to dsls.0 /* init DataSetList for Source */
do dsn=1 to dslt.0 /* init DataSetList for Target */
/**********************************/
If S>=0 then /* Init Step counter */
do
S = S + 1
end
SRCName = WORD(DSLs.dsn,1) /* Variable to identify source dataset */
TGTName = WORD(DSLt.dsn,1) /* Variable to identify target dataset */
/* QUEUE Below is used to generate steps whenever a dataset is read from
Source and Target members contained in JobLib */
if queued()//N_Split=0 then call Write
end /* end do for dsls (source) */
if queued()>0 then call write /* */
cc=bpxwdyn('free dd('dd1')') /* */
end /* end do for dslt (target) */
"delstack" /* */
exit 0 /* */
Sorry for double post, just to provide an example on how the job gets created after running code updated provided in my last update;
So it will generate a STEP1 4 times , then moves to STEP2 4 times.. and so on .. what I'm trying to accomplish here is to setup some variables that for each step it will add a step counter and keep updating that counter once every dataset is referenced.
Updated sample. I have made the assumption that the stems DSNL and DSLT are paired, so that source dataset 1 is in DSNLS.1 and target dataset 1 is in DSNLT.1, and so forth. Hence you only need one loop. Then I understand that you want each and every step number increased. The step number will be right-adjusted 4 bytes wide left-padded with 0. Finally I have used EXECIO directly instead of calling the WRITE routine. I hope that this is what you had in mind.
Code:
"newstack" /* */
Outn=0 /* Clear the variable OutNumber */
JobN=0 /* Clear the variable: JobNumber */
S=0 /* Variable for Step Counter */
do dsn=1 to dsls.0 /* init DataSetList for Source */
SRCName = WORD(DSLs.dsn,1) /* Variable to identify source dataset */
TGTName = WORD(DSLt.dsn,1) /* Variable to identify target dataset */
/* QUEUE Below is used to generate steps whenever a dataset is read from
Source and Target members contained in JobLib */
S = S + 1 /* increase step nr */
QUEUE '//STEP'right(s,4,0)' EXEC PGM=IKJEFT01'
QUEUE '//SYSTSPRT DD SYSOUT=*'
QUEUE '//SYSTSIN DD *'
QUEUE ' PROF NOPREF '
QUEUE ' HSEND FRRECOV FROMCOPYPOOL('CPNAME') - '
QUEUE ' DSNAME('SRCName') - '
QUEUE ' NEWNAME('TGTName') '
S = S + 1 /* increase step nr */
QUEUE '//STEP'right(s,4,0)' EXEC PGM=IKJEFT01,DYNAMNBR=20'
QUEUE '//SYSPROC DD DSN=DB2.CLONE.REXX,DISP=SHR '
QUEUE '//SYSTSPRT DD SYSOUT=* '
QUEUE '//SYSTSIN DD * '
QUEUE ' %SLEEP 5 '
S = S + 1 /* increase step nr */
QUEUE '//STEP'right(s,4,0)' EXEC PGM=IDCAMS '
QUEUE '//SYSPRINT DD SYSOUT=* '
QUEUE '//SYSIN DD * '
QUEUE ' ALTER 'TGTNAME' STORCLAS(SCSTD) '
S = S + 1 /* increase step nr */
QUEUE '//STEP'right(s,4,0)' EXEC PGM=IKJEFT01 '
QUEUE '//SYSTSPRT DD SYSOUT=* '
QUEUE '//SYSTSIN DD * '
QUEUE ' PROF NOPREF '
QUEUE ' HMIGRATE ('TGTNAME') MOVE '
if queued()//N_Split=0 then "execio" queued() "diskr" dd1 /* write*/
end /* end do for dsls (source) */
"execio" queued() "diskr" dd1 "(finis)" /* write and close */
cc=bpxwdyn('free dd('dd1')') /* */
"delstack" /* */
exit 0 /* */
Question, why sub-setting the writes, do you really expect thousands of lines generated?
Hi Willy ! The sample you wrote worked exactly the way I needed !
Thank you very very much for your help, and indeed, these source/target dataset records works in pair, so whenever a record is added on those steps from source, the same record (however with different HLQ) will be added on TGTNAME.
yes, in fact I'm expecting thousand lines to be retrieved but wanted to add the step counter so I can split the jobs across different members whenever it reaches the maximum number of 255 steps (240 is the number I've had in mind).
Also, thanks for explaining each update you've made to the sample, the good thing is that I was able to understand so I believe I'm getting there.
Hi Joerg, thanks for the HSEND WAIT suggestion, I'll look into it and definitely remove the SLEEP step. Nice one !! Just added this sleep step because each step was processed to fast causing HSM to fail the next FRRECOV command.
Joerg, I really have no words to thank you enough for all the good suggestions.
By removing the SLEEP step (it also reduced A LOT the number of steps in the jobs) and adding the HSEND WAIT command on both FRRECOV and MIGRATE it reduced the jobs runtime considerable, below, same number of steps and same datasets ;
Old run using sleep;
2.38 MINUTES EXECUTION TIME
New run without Sleep and adding WAIT parm
0.20 MINUTES EXECUTION TIME
Well, if you really want to reduce the number of steps, then remember that the ALTER command can also be issued from TSO. So you could run everything in one step. Or even from your REXX. Or write a small driver REXX doing one block of HSEND, ALTER and HMIGRATE and then your original REXX would just generate the call to that REXX. Many ways to skin that particular cat.
Thanks for your feedback Willy, the problem I have in running everything in a single step is that, when it comes to multiple files (such as application vsam tablespace files) then I would need basically to double the amount of storage in my storage group, because first I would do the FRRECOV with newname (for all datasets) and then proceed with alter storage class and hsm move. So in order to have this workaround working I would need to treat each dataset at once after proceeding to the next one.
If you allow me to do a one more quick question, I'm trying to use the LMDINIT / LMDLIST for VSAM Cluster/Data however, even by coding the DSNDBC (to list only cluster datasets) still my output shows both cluster and data files, any ideas why ?
Follow the code;
Code:
"ISPEXEC LMDINIT LISTID(IDSID) LEVEL(SAP"SID".DSNDBC.*)"
cnt=1
DO FOREVER
"ISPEXEC LMDLIST LISTID("IDSID") OPTION(LIST) DATASET(DSSID) "
IF RC = 0 THEN
DO
DATA.CNT = DSSID
CNT = CNT + 1
END
ELSE LEAVE
END
Thanks again for all the amazing support and suggestions.
Att.
Interesting, LMDLIST returns the data- and index components, even though they do not match the LEVEL parameter.
You can add STATS(YES) to LMDLIST and then test the ZDLSIZE variable for being null, meaning cluster.
Hi Willy, thanks for you reply again, I've been reading about the STATS(YES) and ZDLSIZE but I'm still trying to make sense of those 2 lol.
Ugh, it is funny how we move from a plan to another when we are trying to design a solution .. I've just removed the N_Split variable because for that one I was spamming multiple jobs based on the number of rows, but once JCL has a 255 steps/execs constraint I have to limit jobs to 255, given the number of vsam cluster datasets I have for my application this might even generate hundreds of jobs.. so I'm looking into some way to run a single step where it invoke the rexx and the rexx script will run the 3 set of commands for all datasets ... right now I was trying to limit the jobs generation by step counter .. I tried IF S = 255 then call Write however this will limit one single job with 255 steps ..
Will look into something like you said (running a single JCL where the rexx is invoked and all commands are processed by the rexx itself).
Anyway, just with the good tips and suggestions this is how the procedure look so far ... (its small work but its decent work ha!)
Code:
/*********************************************************************/
/* During program init insert copy pool name to generate frrecov jobs*/
/*********************************************************************/
SAY 'Insert Copy Pool name eg. DSN$BHSAPPWS$DB / DSN$BHSAPPWS$LG'
PULL CPNAME
CPNAME = CPNAME
/* If user does not provide CPNAME fail exec */
IF CPNAME = '' THEN DO
SAY ' MISSING COPY POOL NAME '
SAY ' PLEASE REPEAT EXEC AND INPUT COPY POOL NAME '
EXIT 16
END
/*-------------------------------------------------------------------*/
CC=BPXWDYN('ALLOC DA('JobLib'('SRCDSN')) SHR RTDDN(DD1)')
"EXECIO * DISKR" DD1 "(STEM DSLs. FINIS)"
CC=BPXWDYN('FREE DD('DD1')') /* Open SRC Member in PDS for data reading */
CC=BPXWDYN('ALLOC DA('JobLib'('TGTDSN')) SHR RTDDN(DD2)')
"EXECIO * DISKR" DD2 "(STEM DSLt. FINIS)"
CC=BPXWDYN('FREE DD('DD2')') /* Open TGT Member in PDS for data reading */
/*-------------------------------------------------------------------*/
"newstack" /* */
Outn=0 /* Clear the variable OutNumber */
JobN=0 /* Clear the variable: JobNumber */
S=0 /* Variable for Step Counter */
/*--------------------------------*/
do dsn=1 to dsls.0 /* init DataSetList for Source */
do dsn=1 to dslt.0 /* init DataSetList for Target */
/**********************************/
SRCName = WORD(DSLs.dsn,1) /* Variable to identify source dataset */
TGTName = WORD(DSLt.dsn,1) /* Variable to identify target dataset */
/* QUEUE Below is used to generate steps whenever a dataset is read from
Source and Target members contained in JobLib */
call JobCard /* CALL JobCard insert jcl card */
"EXECIO" QUEUED() "DISKW" DD1 "(FINIS)"
Return 0
/*-------------------------------------------------------------------*/
/*-------------------------------------------------------------------*/
/* --- Start call JobCard -------------------------------------- */
/*-------------------------------------------------------------------*/
JobCard:
If Jobn>=0 then
do /* */
Jobn = Jobn + 0
end /* */
jcl.1='//FRRC'Jobn' JOB (@HSG,BA01),SP3,CLASS=A,MSGCLASS=H, '
jcl.2='// MSGLEVEL=(1,1),NOTIFY=&SYSUID,REGION=0M '
jcl.3='//*---------------------------------------------------------'
jcl.4='//* Job generated by FRRECOV rexx program '
jcl.5='//*---------------------------------------------------------'
jcl.0=5
/* */
do n=jcl.0 to 1 by -1 /* */
push jcl.n /* */
end /* */
Return
PS. Btw this one doesnt have counter to limit each job by 255 steps.. I couldn't find that yet.. perhaps something simple.. but I need to go deep in my research and study.
Joined: 01 Sep 2006 Posts: 2594 Location: Silicon Valley
I do not like that variable DSN is used for both of these DO clauses. I think it should be two different variable names (though I did not examine the earlier logic thoroughly.
Code:
do dsn=1 to dsls.0 /* init DataSetList for Source */
do dsn=1 to dslt.0 /* init DataSetList for Target */
More likely, there is a one to one correspondence in your dsls. and dslt. stem variables. It seems more plausible that you only need one DO loop here.
Quote:
some way to run a single step where it invoke the rexx
I think you need to rely heavily on ADDRESS TSO to issue your commands.
Code:
Address TSO
'PROF NOPREF '
/*--------------------------------*/
Do dsn=1 to dsls.0 /* init DataSetList for Source */
/* init DataSetList for Target */
SRCName = WORD(DSLs.dsn,1) /* Variable to identify source dataset */
TGTName = WORD(DSLt.dsn,1) /* Variable to identify target dataset */
The double 'DO's have been noted before. Something else, if you just want to look at catalog info then you can use the IGGCSI00 program, which does not incur the cost of reading the VTOC like LMDLIST STATS(YES) does. IBM supplies a sample in SYS1.SAMPLIB(IGGCSIRX) , but there are numerous other implementations. Search this forum, or have a look at my CSIREXX here
Hello Pedro and Willy, wow!! Thank you so much for all the input.
Using Adress TSO just resolved all my problems with step limit in JCL.
The more I work on this project the more I feel like learning and eager to improve the code. So, I've generated an input member (SRCSAP) containing 45K datasets of SAPsid.DSNDBC.** , running a single job with this exec (using address TSO) is working perfectly, but the first runtime took 4 hours to complete (not bad, definitely not bad) but looking to improve the code or the efficiency of the solution, I removed the WAIT parameter (as suggested by Pedro) and was able to reduce the runtime to 3 hours and a half.
Now, on a second test I compiled my rexx and I managed reduce that runtime even more.
Now, I am thinking about serializing my execution (still using address tso) but instead of processing a single source/target member I was thinking about doing a dataset count by 10k or 12k and after each 10-12k generate a new member in DB2.CLONE.NEW.sid;
The current solution (below) will generate one source and one target member and add all source datasets (45k) on the same member, so I am looking for a way to do (perhaps a NESTED LOOP ? ) to generate a new member lets say SRCSPx and TGTSPx once 10 or 12k files are read..
cntid3=1
DO FOREVER
"ISPEXEC LMDLIST LISTID("ID3") OPTION(LIST) DATASET(DSNSAP)"
IF RC = 0 THEN
DO
DATAsap.cntid3 = DSNSAP
cntid3 = cntid3 + 1
END
ELSE LEAVE
END
Something like (not tested, from the top of my head).
Notes:
1 I use BPXWDYN for allocation so that I can retrieve a generated DDname.
2. I use count= 1 to 999999 to save a coount-up operation. not a big saving but still.
3. When count is divisable with 10000 then a reallocation is done
4. I leave the loop for LMDLIST rc<>0, which I find simpler.
I hope this is somewhat what yo are loooking for.
Code:
mbrn=0
ddname=''
Call Realloc
DO count=1 to 999999
"ISPEXEC LMDLIST LISTID("ID3") OPTION(LIST) DATASET(DSNSAP)"
IF RC <> 0 THEN leave
if count//10000=0 then call realloc
DATAsap.count = DSNSAP
END
cc=Bpxwdyn('free dd('ddname')')
... some code ..
ReAalloc:
if ddname<>'' then cc=Bpxwdyn('free dd('ddname')')
mbrn=mbrn+1
cc=Bpxwdyn('alloc da(dataset.name(mbr'mbrn')) shr rtddn(ddname)')
return 0
Just realized that, despite my general preference for BPXWDYN over ALLOCATE, in this case "ALLOCATE DA('dataset.name(mbr"mbrn")) DD(ddname) SHR REUSE" is the simpler option.
Hey Willy, thanks again for your reply, but I'm still having issues to write down the logic to split my DSNDBD files into multiple members.
Whenever you guys give me a sample, what I try to do is, instead of simply copy and paste I try to write down my own logic based on the provided code, in that way, plus reading manuals is the way I'm using to learn rexx (and programming actually).
Now, allow me to paste the entire logic below and explain a little bit (with the current logic I have 2 issues that I'm trying to adress).
So the logic basically asks for a SID (subsystem id) based on that SID, a dataset will be created and then LMDINIT / LMDLIST comes into play to list all datasets based on SAPsid.DSNDBD.** , now, using one database as example, I have 20165 DSNDBD files, so the logic has mainly 2 issues
1) Code is generating 2 members in DB2.CLONE.NEW.sid where SRCSAP1 goes from dataset number 1 to number 10.000 (which is ok) however the second member generated SRCSAP2 will copy the same datasets from row 1 to row 10.000 and continue until it reaches 20.002 (and then it stops). Now the second member is still adding the first 10k files (which were supposed to be on member 1 only).
2) Since its doing "IF count // 10001=0 THEN CALL Realloc" count goes only until 20.002 files, so its not writing all 20.165 files into the members.
Now, I have a guess about issue number 1, since I'm doing LMDINIT / LMDLIST, I think between the loop (from SRCSAP1 to SRCSAP2) the list is deleted and recreated so it will add all the 20002 datasets in member 2 ? I thought about creating a 3rd member containing the full list and then split this list into 2 new members splitting by 10.000 each ? Not sure if that would be the correct approach to follow ?
Here's the current code;
Code:
/* REXX */
SAY 'PLEASE INFORM A SUBSYSTEM ID NAME E.G. PWS , YPE , YPC '
SAY 'Member number is not required (Do not use E.G. YPE1 , YPC2) '
PULL SID
IF SID = '' THEN DO
SAY 'MISSING SUBSYSTEM NAME, PROGRAM WILL END'
EXIT 16
END
ELSE SAY "DATASET DB2.CLONE.NEW."SID" WILL BE ALLOCATED"
CALL ALLOC_DS
ALLOC_DS: /* This procedure will create the new dataset that will store
Source / Target files and also FRRECOV jobs */
DSN = SYSDSN(DB2.CLONE.NEW.""SID"") /* When SID specified, create dataset */
IF DSN = 'OK' THEN DO /* If DSN already exist, program will exit */
SAY "DATASET ALREADY EXISTS. EXITING PROGRAM, GOODBYE!"
EXIT 4
END
ELSE /* If DSN does not exist, create and continue exec */
"ALLOC DA(DB2.CLONE.NEW."SID") NEW REU RECFM(F B) LRECL(80)",
"DSORG(PO) SPACE(10,10) CYLINDERS UNIT(SYSDA) DSNTYPE(LIBRARY)"
SAY "DATASET DB2.CLONE.NEW."SID" ALLOCATED SUCCESSFULY "
SAY
SAY
SAY
UPDATE_DS:
/* UPDATE_DS procedure will list all DB2 source files and create a member
for each set of datasets */
/* FETCH STEP 3: Will fetch data based on SAPSID parsed argument */
SAY "CREATING MEMBER (SRCSAP) TO LIST SOURCE DB2 DATASETS"
SAY "CREATING MEMBER (TGTSAP) WITH RENAME FROM SRCSAP FILE"
SAY
SAY "LOADING DATASET LIST IN SRCSAP MEMBER. PLEASE BE PATIENT"
SAY
mbn = 0
CALL Realloc
DO count = 1 to 999999
"ISPEXEC LMDLIST LISTID("ID3") OPTION(LIST) DATASET(DSNSAP)"
IF RC <> 0 THEN CALL LIST_DS
IF count // 10001=0 THEN CALL Realloc
DATAsap.count = DSNSAP
END