%macro ARMJOIN ( libin=WORK, libout=WORK, txnds=YES, updtds=YES ); %*--------------------------------------------------------------------*; %* Copyright (C) 1998 by SAS Institute Inc., Cary, NC 27512-8000 *; %* *; %* Name: armjoin.sas *; %* Support: SAS Technical Support *; %* Product: base *; %* Purpose: ARM Join macro - will read the six output sas datasets *; %* from %ARMPROC and join them into various views and *; %* datasets for easier analysis. *; %* *; %* Input: the six %ARMPROC sas datasets *; %* Output: Variable number of output views and datasets: *; %* *; %* 1) APP - contains 1 observation for every application *; %* *; %* 2) TXNVIEW - a view that presents one observation for *; %* each ARM transaction for all applications. Each *; %* observation contains start/stop, elapsed and cpu *; %* times. *; %* *; %* 3) TXNxxxx - same as TXNVIEW, but for a particular *; %* application. xxxx is a sequentially assigned *; %* numeric. There are the same number of *; %* TXNxxxx datasets as there are applications. *; %* *; %* 4) UPDTVIEW - a view that presents multiple observations *; %* for each ARM transaction for all applications. Each *; %* observation contains the ARM call datetime, an ARM *; %* call sequence id, and, if applicable, elapsed time, *; %* cpu time, and update data. *; %* *; %* 5) UPDTxxxx - similar to TXNxxxx, only created from *; %* UPDTVIEW. *; %* *; %* The TXNxxxx datasets are easier to use for analysis *; %* individual ARM transactions since all information about *; %* a transaction is collapsed into one observation. *; %* However, the TXNxxxx datasets do not contain any *; %* information from ARM_UPDATE calls. *; %* *; %* The UPDTxxxx datasets are a little more difficult to *; %* use, since information about a single transaction is *; %* spread over several observations. However, these *; %* datasets contain information from all ARM_UPDATE calls *; %* and enable easy graphing of transaction timelines. *; %* *; %* *; %* Parms: positional: *; %* keyword: libin - libref of input datasets. Default *; %* is WORK. *; %* libout - libref of output datasets. Default *; %* is WORK. *; %* txnds - whether or not to create transaction *; %* datasets. Default is YES. *; %* updtds - whether or not to create update *; %* datasets. Default is YES. *; %* *; %* *; %* Notes: This is the same version that is included in SAS 9.2 *; %* *; %* End *; %*--------------------------------------------------------------------*; %global _armnapp; %local _armlibi; %local _armlibo; %local _armtxds; %local _armupds; %let _armlibi = %bquote(&libin); %let _armlibo = %bquote(&libout); %let _armtxds = %upcase(%bquote(&txnds)); %let _armupds = %upcase(%bquote(&updtds)); %*--------------------------------------------------------------------*; %* Sort the datasets from %armproc *; %*--------------------------------------------------------------------*; proc sort data=&_armlibi..init; by appid initdt; run; proc sort data=&_armlibi..getid; by appid clsid txshdl getiddt; run; proc sort data=&_armlibi..start; by appid clsid txshdl startdt; run; proc sort data=&_armlibi..update; by appid clsid txshdl updtdt; run; proc sort data=&_armlibi..stop; by appid clsid txshdl stopdt; run; proc sort data=&_armlibi..end; by appid enddt; run; proc datasets library=&_armlibi; modify getid; index create clsix=(appid clsid txshdl); run; quit; %*--------------------------------------------------------------------*; %* Create a view of a single transaction dataset for all *; %* applications that merges start/stop info. *; %*--------------------------------------------------------------------*; proc sql; create view &_armlibo..txnview as select s.appid, s.clsid, s.txshdl, s.parcls, s.parhdl, s.startdt, p.stopdt, p.txstat, g.txname, g.txdet, p.txcpu as txstopcpu, s.txcpu as txstrtcpu, (p.txcpu - s.txcpu) as txcpu label='Txn System CPU' format=mmss10.5, p.txusrcpu as stopusr, s.txusrcpu as strtusr, (p.txusrcpu - s.txusrcpu) as txusrcpu label='Txn User CPU' format=mmss10.5, timepart(startdt) as starttm label='Time of Txn' format=time8., p.stopdt - s.startdt as txelap label='Elapsed Time' format=mmss10.5, g.metric1, g.metrDesc1, s.metrVal1 as strtMetrVal1, p.metrVal1 as stopMetrVal1, g.metric1, g.metrDesc2, s.metrVal2 as strtMetrVal2, p.metrVal2 as stopMetrVal2, g.metric1, g.metrDesc3, s.metrVal3 as strtMetrVal3, p.metrVal3 as stopMetrVal3, g.metric1, g.metrDesc4, s.metrVal4 as strtMetrVal4, p.metrVal4 as stopMetrVal4, g.metric1, g.metrDesc5, s.metrVal5 as strtMetrVal5, p.metrVal5 as stopMetrVal5, g.metric1, g.metrDesc6, s.metrVal6 as strtMetrVal6, p.metrVal6 as stopMetrVal6, g.string1, g.strDesc1, s.strVal1, p.strVal1 from &_armlibi..start s, &_armlibi..stop p, &_armlibi..getid g where s.appid = p.appid and s.txshdl = p.txshdl and s.clsid = g.clsid order by 1, 2, 3; quit; %*--------------------------------------------------------------------*; %* Create a view of a single merged transaction dataset for all *; %* applications that merges update info. This code does a lookahead *; %* read to calculate the "delta" elapsed and cpu times, i.e., *; %* the duration of the ARM call in a transaction. *; %*--------------------------------------------------------------------*; data &_armlibo..updtview ( keep= appid clsid txshdl txusrcpu txcpu txstat updtdata calldt call deltelap deltusr deltcpu noncpu txname txdet metric1 - metric6 string1 metrDesc1 - metrDesc6 strDesc1 metrVal1 - metrVal6 strVal1 fmt2Buff ) / view=&_armlibo..updtview; format calldt datetime23.3; format deltelap deltusr deltcpu noncpu time14.3; label calldt = 'ARM Call datetime'; label call = 'Call Sequence Identifier'; label deltelap = 'Delta Elapsed Time'; label deltusr = 'Delta User CPU Time'; label deltcpu = 'Delta System CPU Time'; label noncpu = 'Non-CPU Time'; set &_armlibi..getid (in=inCls keep=appid clsid txname txdet txshdl metric1 - metric6 string1 metrDesc1 - metrDesc6 strDesc1 ) &_armlibi..start (in=inStart) &_armlibi..update (in=inUpdt) &_armlibi..stop (in=inStop) end=done; by appid clsid txshdl; length svMetric1 - svMetric6 $8 svMetrDesc1 - svMetrDesc6 $44 svStr1 $8 svStrDesc1 $44 svtxName $128 svtxDet $128 fmt2Buff $ 1020 ; retain svMetric1 - svMetric6 svMetrDesc1 - svMetrDesc6 svStr1 svStrDesc1 ' ' svtxName svtxDet ' ' ; array ar_metric (*) $ Metric1 - metric6 string1 metrDesc1 - metrDesc6 strDesc1; array ar_svMetric (*) $ svMetric1 - svMetric6 svStr1 svMetrDesc1 - svMetrDesc6 svStrDesc1; if first.clsid then do; if inCls then do; svtxName = txName; svtxDet = txDet; do _I_ = 1 to hbound(ar_svmetric); ar_svMetric (_I_) = ar_metric (_I_); end; delete; /* not included in output */ end; else do; svtxName = ' '; svtxDet = ' '; do _I_ = 1 to hbound(ar_svMetric); ar_svMetric (_I_) = ' '; end; end; end; retain calldt deltcpu deltelap deltusr noncpu stopdt strtcpu strtelap strtusr updtdata updtdt; if first.txshdl then do; updtcnt = 0; if ^inStart then do; calldt = 0; deltcpu = 0; deltelap = 0; deltusr = 0; noncpu = 0; stopdt = 0; strtcpu = 0; strtelap = 0; strtusr = 0; end; end; updtcnt + 1; if inStart then do; calldt = startdt; deltcpu = 0; deltelap = 0; deltusr = 0; strtcpu = txcpu; strtusr = txusrcpu; strtelap = startdt; updtdata = 'START'; end; if inUpdt then do; calldt = updtdt; deltcpu = txcpu - strtcpu; deltelap = updtdt - strtelap; deltusr = txusrcpu - strtusr; noncpu = deltelap - (deltcpu + deltusr); strtcpu = txcpu; strtelap = updtdt; strtusr = txusrcpu; updtdata = 'UPDATE'; end; if inStop or last.txshdl then do; calldt = stopdt; deltcpu = txcpu - strtcpu; deltelap = stopdt - strtelap; deltusr = txusrcpu - strtusr; noncpu = deltelap - (deltcpu + deltusr); updtdata = 'STOP'; end; txName = svtxName; txDet = svtxDet; do _I_ = 1 to hbound(ar_metric); ar_metric (_I_) = ar_svMetric (_I_); end; call = 'UPD' || left(trim(put(updtcnt,z5.))); if deltelap < 0 then deltelap = 0; if deltcpu < 0 then deltcpu = 0; if deltusr < 0 then deltusr = 0; if noncpu < 0 then noncpu = 0; return; /* to top of data step */ run; %*--------------------------------------------------------------------*; %* Build end of application counts and stats *; %*--------------------------------------------------------------------*; data &_armlibo..startTot; set &_armlibi..start; by appid; keep appid appntxst; label appntxst = '# Started Txns'; appntxst + 1; if last.appid then do; output; appntxst = 0 ; end; data &_armlibo..stopTot; set &_armlibi..stop; by appid; keep appid appntxsp appntx0 appntx1 appntx2 appmintx appmaxtx ; label appntxsp = '# Stopped Txns'; label appntx0 = '# Txn rc=0'; label appntx1 = '# Txn rc=1'; label appntx2 = '# Txn rc=2'; label appmintx = 'Min Txn Time'; label appmaxtx = 'Max Txn Time'; retain appmintx 99999999 appmaxtx 0; appntxsp + 1; if txstat = 0 then appntx0 + 1; else if txstat = 1 then appntx1 + 1; else if txstat = 2 then appntx2 + 1; else /* 0 or missing */ appntx0 + 1; appmintx = min(appmintx,txusrcpu); appmaxtx = max(appmaxtx,txusrcpu); if last.appid then do; output; appntxsp = 0 ; appntx0 = 0; appntx1 = 0; appntx2 = 0; appmintx = 999999999; appmaxtx = 0; end; data &_armlibo..tranTot; set &_armlibi..getid; by appid; keep appid appncls; label appncls = '# Txn Classes'; appncls + 1; if last.appid then do; output; appncls = 0 ; end; data &_armlibo..updtTot; set &_armlibi..update; by appid; keep appid appntxup; label appntxup = '# Txn Updates'; appntxup + 1; if last.appid then do; output; appntxup = 0 ; end; data &_armlibo..totals; merge &_armlibo..startTot &_armlibo..stopTot &_armlibo..updtTot &_armlibo..tranTot; by appid; run; %*--------------------------------------------------------------------*; %* Merge the init and end datasets to create the app dataset. *; %* Only applications with both an ARMINIT and ARMEND are output. *; %* A variable is created that is an ordinal number for the app. *; %*--------------------------------------------------------------------*; data &_armlibo..app; merge &_armlibi..init (in=ininit) end=lastinit &_armlibi..end (in=inend) end=lastend &_armlibo..Totals (in=inTot) end=lastTot ; by appid; keep appid initdt enddt appname appuser appncls appntxst appntxsp appntxup appntx0 appntx1 appntx2 appelap appavgrt appmintx appmaxtx appno ; label appno = 'App Number'; length appavgrt appmintx appmaxtx appncls appntxst appntxsp appntxup appelap appntx0 appntx1 appntx2 8; format appavgrt appmintx appmaxtx time14.3; initdone = 0; enddone = 0; nout = 0; initwarn = 0; endwarn = 0; /* calculate appelap */ appelap = enddt - initdt; /* have begining and ending application transactions */ if (ininit and inend) and (initdt <= enddt) then do; nout = nout + 1; appno = nout; /* calculate average */ if appntxst > 0 then appavgrt = appelap / appntxst; else appavgrt = 0; output; return; end; if (ininit and ^inend) then do; initwarn = initwarn + 1; end; if (^ininit and inend) then do; endwarn = endwarn + 1; end; if (ininit and inend) and (initdt > enddt) then do; put 'ERROR: Bad data, ARM_INIT datetime > ARM_END datetime'; put 'ERROR: %armjoin execution halted.'; stop; end; if lastinit or lastend then do; if (initwarn > 0) then do; put 'WARNING: Some ARMINIT data discarded due to ' 'no matching ARMEND calls.'; put 'WARNING: Number of records discarded = ' initwarn; end; if (endwarn > 0) then do; put 'WARNING: Some ARMEND data discarded due to ' 'no matching ARMINIT calls.'; put 'WARNING: Number of records discarded = ' endwarn; end; stop; end; run; proc datasets library=&_armlibo; delete startTot stopTot updtTot tranTot Totals; quit; run; %*--------------------------------------------------------------------*; %* See how many applications we have. *; %*--------------------------------------------------------------------*; data _null_; if 0 then set &_armlibo..app nobs=nobs; call symput( '_armnapp', left(put(nobs,8.)) ); stop; run; %*--------------------------------------------------------------------*; %* Create transaction (TXN*) datasets for each application. *; %*--------------------------------------------------------------------*; %if ( (%substr(&_armtxds,1,1) = Y) and ( %eval(&_armnapp > 0) ) ) %then %do; data %do i = 1 %to &_armnapp; &_armlibo..txn&i (keep= appid clsid txshdl startdt stopdt starttm txusrcpu txcpu txstat txname txdet ) %end; %str(;) retain n; array tappid{&_armnapp} _temporary_; array tinitdt{&_armnapp} _temporary_; array tenddt{&_armnapp} _temporary_; array tappno{&_armnapp} _temporary_; link loadapp; do while (1=1); outapp = 0; set &_armlibo..txnview end=endtxn; do i = 1 to &_armnapp; if (appid = tappid{i}) and (startdt >= tinitdt{i}) and (stopdt <= tenddt{i}) then outapp = tappno{i}; end; select ( outapp ); %do i = 1 %to &_armnapp; when ( &i ) output &_armlibo..txn&i; %end; otherwise; end; /* select */ if endtxn then stop; end; /* big do while */ loadapp: n = 0; do while (1=1); set &_armlibo..app end=endapp; n = n + 1; tappid{n} = appid; tinitdt{n} = initdt; tenddt{n} = enddt; tappno{n} = appno; if endapp then return; end; return; run; %end; %*--------------------------------------------------------------------*; %* Create update (UPDT*) datasets for each application. *; %*--------------------------------------------------------------------*; %if ( (%substr(&_armupds,1,1) = Y) and ( %eval(&_armnapp > 0) ) ) %then %do; data %do i = 1 %to &_armnapp; &_armlibo..updt&i (keep= appid clsid txshdl calldt call txusrcpu txcpu txstat updtdata txname txdet deltelap deltcpu deltusr ) %end; %str(;) retain n; array tappid{&_armnapp} _temporary_; array tinitdt{&_armnapp} _temporary_; array tenddt{&_armnapp} _temporary_; array tappno{&_armnapp} _temporary_; link loadapp2; do while (1=1); outapp = 0; set &_armlibo..updtview end=endupdt; do i = 1 to &_armnapp; if (appid = tappid{i}) and (calldt >= tinitdt{i}) and (calldt <= tenddt{i}) then outapp = tappno{i}; end; select ( outapp ); %do i = 1 %to &_armnapp; when ( &i ) output &_armlibo..updt&i; %end; otherwise; end; /* select */ if endupdt then stop; end; /* big do while */ loadapp2: n = 0; do while (1=1); set &_armlibo..app end=endapp; n = n + 1; tappid{n} = appid; tinitdt{n} = initdt; tenddt{n} = enddt; tappno{n} = appno; if endapp then return; end; return; run; %end; %mend armjoin;